Upload
Download free for 30 days
Login
Submit Search
Cast SDK for Flutter
3 likes
1,619 views
K
KojiYamada22
Cast SDK for Flutter Meetup Slide
Engineering
Read more
1 of 31
Download now
Downloaded 15 times
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
More Related Content
PDF
Serverless AWS構成でセキュアなSPAを目指す
Masayuki Kato
PDF
IDガバナンス&管理の基礎
Hitachi, Ltd. OSS Solution Center.
PDF
ユーザーストーリーの分割
Arata Fujimura
PDF
ZOZOTOWNのアーキテクトという役割を紹介します
Hiromasa Oka
PDF
PostgreSQLのバグとの付き合い方 ~バグの調査からコミュニティへの報告、修正パッチ投稿まで~(PostgreSQL Conference Japa...
NTT DATA Technology & Innovation
PDF
今どきの若手育成にひそむ3つの思いこみ
Mariko Hayashi
PDF
Apache Arrow Flight – ビッグデータ用高速データ転送フレームワーク #dbts2021
Kouhei Sutou
PDF
コンテナ環境でJavaイメージを小さくする方法!
オラクルエンジニア通信
Serverless AWS構成でセキュアなSPAを目指す
Masayuki Kato
IDガバナンス&管理の基礎
Hitachi, Ltd. OSS Solution Center.
ユーザーストーリーの分割
Arata Fujimura
ZOZOTOWNのアーキテクトという役割を紹介します
Hiromasa Oka
PostgreSQLのバグとの付き合い方 ~バグの調査からコミュニティへの報告、修正パッチ投稿まで~(PostgreSQL Conference Japa...
NTT DATA Technology & Innovation
今どきの若手育成にひそむ3つの思いこみ
Mariko Hayashi
Apache Arrow Flight – ビッグデータ用高速データ転送フレームワーク #dbts2021
Kouhei Sutou
コンテナ環境でJavaイメージを小さくする方法!
オラクルエンジニア通信
What's hot
(20)
PDF
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
Daisuke Morishita
PPTX
え、まって。その並列分散処理、Kafkaのしくみでもできるの? Apache Kafkaの機能を利用した大規模ストリームデータの並列分散処理
NTT DATA Technology & Innovation
PDF
Active Directory 侵害と推奨対策
Yurika Kakiuchi
PDF
Apache Arrow - データ処理ツールの次世代プラットフォーム
Kouhei Sutou
PPTX
BLEACH -Brave Souls- 3DUI演出の実装事例
KLab Inc. / Tech
PDF
Spring Security 5.0 解剖速報
Takuya Iwatsuka
PDF
Keycloak拡張入門
Hiroyuki Wada
PDF
What's new in Spring Batch 5
ikeyat
PDF
なぜソフトウェアアーキテクトが必要なのか - Devlove 20110423
Yusuke Suzuki
PPTX
初心者向けMongoDBのキホン!
Tetsutaro Watanabe
PDF
テスト文字列に「うんこ」と入れるな
Kentaro Matsui
PDF
分散トレーシング技術について(Open tracingやjaeger)
NTT Communications Technology Development
PDF
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
NTT DATA Technology & Innovation
PDF
JDKの選択肢とサーバーサイドでの選び方
Takahiro YAMADA
PDF
DevOps Overview
IIJ
PPTX
Keycloak入門
Hiroyuki Wada
PDF
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
Y Watanabe
PDF
ChatGPTのデータソースにPostgreSQLを使う(第42回PostgreSQLアンカンファレンス@オンライン 発表資料)
NTT DATA Technology & Innovation
PPTX
コンテナネットワーキング(CNI)最前線
Motonori Shindo
PDF
Java + React.jsでSever Side Rendering #reactjs_meetup
Toshiaki Maki
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
Daisuke Morishita
え、まって。その並列分散処理、Kafkaのしくみでもできるの? Apache Kafkaの機能を利用した大規模ストリームデータの並列分散処理
NTT DATA Technology & Innovation
Active Directory 侵害と推奨対策
Yurika Kakiuchi
Apache Arrow - データ処理ツールの次世代プラットフォーム
Kouhei Sutou
BLEACH -Brave Souls- 3DUI演出の実装事例
KLab Inc. / Tech
Spring Security 5.0 解剖速報
Takuya Iwatsuka
Keycloak拡張入門
Hiroyuki Wada
What's new in Spring Batch 5
ikeyat
なぜソフトウェアアーキテクトが必要なのか - Devlove 20110423
Yusuke Suzuki
初心者向けMongoDBのキホン!
Tetsutaro Watanabe
テスト文字列に「うんこ」と入れるな
Kentaro Matsui
分散トレーシング技術について(Open tracingやjaeger)
NTT Communications Technology Development
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
NTT DATA Technology & Innovation
JDKの選択肢とサーバーサイドでの選び方
Takahiro YAMADA
DevOps Overview
IIJ
Keycloak入門
Hiroyuki Wada
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
Y Watanabe
ChatGPTのデータソースにPostgreSQLを使う(第42回PostgreSQLアンカンファレンス@オンライン 発表資料)
NTT DATA Technology & Innovation
コンテナネットワーキング(CNI)最前線
Motonori Shindo
Java + React.jsでSever Side Rendering #reactjs_meetup
Toshiaki Maki
Ad
Cast SDK for Flutter
1.
Cast SDK for
Flutter
2.
山田 幸司 koji.yamada@gree.net グリー株式会社 開発本部 /
インフラストラクチャ部 / ディベロップメント オペレーションズグループ / サービスディベロップメント チーム 所属 業務内容 VRアプリフロントエンド担当 最近のお仕事はFlutter / Unity / Unreal Engine 4など
3.
アジェンダ ● Chromecastについて ○ Chromecastとは? ○
Cast SDKについて ● Cast SDK for Flutterを作る ○ Flutterでプラグインを作る準備やインストール方法など ○ 構成から実装のおおまかな流れ ○ 実装方法などについて
4.
Chromecast ● Googleが開発・販売する小型のデバイス ● HDMI端子に接続/Wi-Fiを介してスマートフォンやタブレットなどで再生して いる動画、写真、ウェブサイトなどをディスプレイに表示出来るデバイス ●
ミラーリングとはやや違ってスマートフォンをコントローラとして扱い動画 の再生や停止などもできる ● 値段は5,000円程度(4K対応のUltraは10,000円程度) ● アプリで利用するにはCast SDKを組み込む必要がある ※↑アプリが対応しているかつ周辺にChromecast がある場合はこのアイコンが表示される
5.
Cast SDK ● Googleが提供するChromecast用のSDK ○
https://developers.google.com/cast/ ● Android/iOS/Chrome(ブラウザ)に対応 ○ Android → Java/Kotlin ○ iOS → Objective-C/Swift ○ Chrome → Javascript ● 公式のFlutter用Cast SDKはまだ存在しない ● Flutter版もいつか対応するかも ○ GitHubのissuesはあがっている ○ https://github.com/flutter/flutter/issues/18212
6.
Cast SDK for
Flutter ● 今日の話 ○ Flutter用のChromecast Pluginを作る話 ○ ChromecastのAndroid/iOS両方に対応するアプリをさく っと作れる ● 実装するもの ○ 送信側(Sender)と受信側(Receiver)を作る必要があ りますが、今回はSenderアプリのみについて ○ 動画コントロールパネルやキャストボタンなどのUI部分 はFlutterで実装 ○ 動画をChromecastにキャスト/再生と停止ができる
7.
準備 ● Plugin Packageプロジェクトの作成 ○
$ flutter create --template=plugin -i swift -a kotlin [プロジェクト名] 例) chrome_cast_plugin Android/build.gradle … dependencies { implementation 'com.google.android.gms:play-services-cast-framework:16.1.2' ... iOS/Podfile … target 'Runner' do pod 'google-cast-sdk', '~> 4.3' ... Sync Now pod install
8.
構成 app main.dart lib chrome_cast_ plugin.dart Flutter/Dart Android/Kotlin iOS/Swift CastOptionsProvider.kt ChromeCastPlugin.kt SwiftChromeCast Plugin.swift Cast SDK for
Android gms:play-services-cast Cast SDK for iOS google-cast-sdk ● main.dart ○ キャストボタンなどのUI表示 ● chrome_cast_plugin.dart ○ Kotlin/Swiftとのつなぎ プラグイン側
9.
Pluginで実装すること 1. Cast Contextの初期化 2.
Cast Dialogの表示 3. Cast Buttonの表示 4. リスナーの登録 5. 動画をキャストする 6. キャストした動画を制御する
10.
Cast Contextの初期化 ● Chromecast
の機能を使うために必要 ● Android ○ OptionsProviderインタフェースを実装したクラスの作成 ○ レシーバーIdのCastOptionsのインスタンスを作る ○ CastContext.getSharedInstance(Activity)で初期化 ● iOS ○ レシーバーIdのGCKCastOptionのインスタンスを作る ○ GCKCastContext.setSharedInstanceWith(GCKCastOption)で初期化 ● 初期化タイミング ○ pluginのregisterWith / register
11.
class ChromeCastPlugin(private val
pActivity: Activity, private val pChannel: MethodChannel): … companion object { @JvmStatic fun registerWith(registrar: Registrar) { CastContext.getSharedInstance(pActivity) … Android/Kotlin static const MethodChannel _channel = const MethodChannel('chrome_cast_plugin'); chrome_cast_plugin.dart public class SwiftChromeCastPlugin: NSObject, FlutterPlugin { … public static func register(with registrar: FlutterPluginRegistrar) { let tCriteria = GCKDiscoveryCriteria(applicationID: [レシーバーのId]) let tOption = GCKCastOptions(discoveryCriteria: tCriteria) GCKCastContext.setSharedInstanceWith(tOption) … iOS/Swift
12.
class CastOptionsProvider :
OptionsProvider { … override fun getCastOptions(context: Context): CastOptions? { val tAppId = context.resources.getIdentifier("chromecast_app_id", "string", context.packageName) … return CastOptions.Builder() .setReceiverApplicationId(context.getString(tAppId)) .build() } … } Android/Kotlin Cast Contextの初期化(Android CastOptionsProvider.kt) <?xml version="1.0" encoding="utf-8"?> <resources> <string name="chromecast_app_id">[レシーバーId]</string> </resources> app/res/values/string.xml
13.
Cast Dialogの表示 ● キャストしてないとき ○
キャスト可能なデバイス一覧を表示 ● キャストしてるとき ○ メディア情報、音量などを表示 ● Android(少々手間がかかる) ○ 現在キャスト中かどうかを判定して出し分けが必要 ○ 現在のキャストセッションがあれば MediaRouteControllerDialog ○ 無い場合はMediaRouteChooserDialogを呼ぶ ● iOS ○ presentCastDialogを呼ぶだけ
14.
private fun showDialog(pCall:
MethodCall, pResult: Result) { val tCastSession = CastContext.getSharedInstance()?.sessionManager?.currentCastSession if (tCastSession != null) { val tControllerDialog = MediaRouteControllerDialog(pActivity, R.style.CastControllerDialogTheme) tControllerDialog.show() } else { val tChooserDialog = MediaRouteChooserDialog(pActivity, R.style.CastMediaRouterTheme) tChooserDialog.routeSelector = CastContext.getSharedInstance()?.mergedSelector!! tChooserDialog.show() } } Android/Kotlin Future<void> showCastDialog() async { await _channel.invokeMethod('ShowCastDialog'); } chrome_cast_plugin.dart private func showDialog(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tContext:GCKCastContext = GCKCastContext.sharedInstance() tContext.presentCastDialog() } iOS/Swift
15.
動画をキャストする ● 動画をキャストするために必要な情報をFlutterからPlugin側に渡す ● 手順 ○
[1] キャストするための必要なメタデータ(Metadata)を作成するためのパ ラメータ ■ タイトル、キャストダイアログに出す画像など ○ [2] 作成したメタデータからメディア情報(MediaInfo)を作成するためのパ ラメータ ■ 動画のコンテンツタイプ(mp4など)、レシーバーに渡す独自のJsonデータなど ○ [3] 必要に応じて読み込み時のオプションを作成するためのパラメータ ■ 再生位置、自動再生するかどうかなど ○ [4] 動画をデバイスにキャスト
16.
Future<bool> startCast(Media media)
async { bool _result = await _channel.invokeMethod('StartCast', { 'Uri': media.url, 'Title': media.title, 'Subtitle': media.subTitle, 'Studio': media.studio, 'ContentType': media.contentType, 'Images': { 'Dialog': { 'Url': media.dialogThumbnail.url, 'Width': media.dialogThumbnail.width, 'Height': media.dialogThumbnail.height }, 'Notification': { 'Url': media.notificationThumbnail.url, } }, chrome_cast_plugin.dart 'IsAuto': media.isAuto, 'PlayPosition': media.position, 'IsLive': media.isLive, 'CustomData': media.customData }); return _result; } ... class Media { Media({ this.url, this.title, this.subTitle, this.studio, this.contentType:, this.customData: const {}, this.isAuto, this.position, this.isLive, this.dialogThumbnail, this.notificationThumbnail, }) : ...
17.
private fun startCast(pCall:
MethodCall, pResult: Result) { val tArguments = pCall.arguments as? Map<String, Any> … // [1] メタデータの設定 val tMovieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) tMovieMetadata.putString(MediaMetadata.KEY_TITLE, tTitle) tMovieMetadata.addImage(WebImage(Uri.parse(tDialogUrl), tWidth, tHeight)) tMovieMetadata.addImage(WebImage(Uri.parse(tNotificationUrl))) … // [2] メディア情報の作成 val tMediaInfo = MediaInfo.Builder(tUri) .setStreamType(tStreamType) .setContentType(tContentType) .setMetadata(tMovieMetadata) .setCustomData(tJsonObject) .build() … Android/Kotlin // [3] 読み込み時のオプション val tMediaLoadOptions: MediaLoadOptions = MediaLoadOptions.Builder() .setAutoplay(tIsAuto) .setPlayPosition(tPlayPosition.toLong()) .build() … // [4] Chromecastにキャスト val tIsSuccess = (tCastSession?.remoteMediaClient?.load(tMediaInfo, tMediaLoadOptions) != null) pResult.success(tIsSuccess) }
18.
private func startCast(_
pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tArguments = pCall.arguments as? Dictionary<String, Any> … // [1] メタデータの設定 let tMetadata:GCKMediaMetadata = GCKMediaMetadata(metadataType: GCKMediaMetadataType.movie) tMetadata.setString(tTitle, forKey: kGCKMetadataKeyTitle) tMetadata.addImage(GCKImage(url: URL(string: (tDialogUrl)), width: tWidth, height: tHeight)) … // [2] メディア情報の作成 let tBuilder = GCKMediaInformationBuilder.init(contentURL: tParseUrl) tBuilder.contentID = tUri tBuilder.streamType = tStreamType tBuilder.contentType = tContentType tBuilder.metadata = tMetadata tBuilder.customData = tJsonData … // [3] 読み込み時のオプション let tMediaLoadOption = GCKMediaLoadOptions(); tMediaLoadOption.autoplay = tIsAuto tMediaLoadOption.playPosition = tPlayPosition … // [4] Chromecastにキャスト var tIsSuccess = true; if (tCastSession?.remoteMediaClient? .loadMedia(tMediaInfo, with: tMediaLoadOption)) != nil { tIsSuccess = true; } else { tIsSuccess = false } pResult(tIsSuccess) } iOS/Swift
19.
実演(動画)
20.
● 仕様 ○ Android
/ iOSで動作するFlutterプラグイン及びアプリ ■ Flutter : 1.2.1 ■ Android : Kotlin 1.3.11 / gms-play-service : 16.1.2 ■ iOS : Swift 4.2.1 / google-cast-sdk : 4.3.5 ● 開発エディタ ○ VSCode ○ Android Studio ○ Xcode ● Google Cast Document ○ https://developers.google.com/cast/docs/developers ● Google Cast GitHub(Android/iOS) ○ https://github.com/googlecast/CastVideos-ios ○ https://github.com/googlecast/CastVideos-android ここから先のスライドは補足
21.
補足 iOS/Xcodeで開発する場合の注意 ● Xcode10以上、iOS
12以降をターゲットにしている場合 ● Access Wifi InformationをONにする
22.
Cast Buttonの表示 ● キャスト可能なデバイスがあるときはアイコンを表示、接続中は接続中のア イコン、キャスト可能なデバイスがない場合は非表示に切り替える ●
Cast StatusをAndroid/iOSのプラグイン側から取得 ● Cast Statusの状態をもとにFlutter側でアイコンの表示・非表示を切り替える キャストしてない キャスト中
23.
private fun getCastState(pCall:
MethodCall, pResult: Result) { val tCastContext = CastContext.getSharedInstance() val Status = tCastContext.castState pResult.success(tStatus) } Android/Kotlin enum CastStatus { NO_DEVICES_AVAILABLE, NOT_CONNECTED, CONNECTING, CONNECTED, } … Future<CastStatus> getCastStatus() async { CastStatus _castStatus; int _result = await _channel.invokeMethod('GetCastStatus'); _castStatus = _getCastStateByValue(_result); return _castStatus; } … CastStatus _getCastStateByValue(int result) { switch (result) { case 1: _castStatus = CastStatus.NO_DEVICES_AVAILABLE; … break; case 2: _castStatus = CastStatus.NOT_CONNECTED; … break; … chrome_cast_plugin.dart private func getCastStatus(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tCastContext = GCKCastContext.sharedInstance() let tStatus = tCastContext.castState.rawValue pResult(tStatus) } iOS/Swift
24.
リスナーの登録 ● SessionManager Listener ○
アプリ全体のキャストセッションを管理 ○ SessionManagerに登録する ■ アプリがセッションを開始したとき ■ アプリがセッションとの接続を停止したとき など… ● RemoteMediaClient Listener ○ アプリと現在キャストしているデバイス(Chromecast)のメディアのセッションを管理 ○ CastSessionに登録する ■ 動画などをキャストをしたとき ■ キャストした動画に変更があったとき など… ● Flutter側の実装は必要なし ○ ※リスナーの中身の実装は今回は省略
25.
class ChromeCastPlugin(private val
pActivity: Activity, private val pChannel: MethodChannel): … CastContext.getSharedInstance()?.sessionManager? .addSessionManagerListener(*fugafura, CastSession::class.java) ... CastContext.getSharedInstance()?.sessionManager?.currentCastSession? .remoteMediaClient?.registerCallback(*hogehoge) …. Android/Kotlin public class SwiftChromeCastPlugin: NSObject, FlutterPlugin, GCKSessionManagerListener, GCKRemoteMediaClientListener { … GCKCastContext.sharedInstance().sessionManager.add(*hogehoge) …. GCKCastContext.sharedInstance().sessionManager.currentSession?.remoteMediaClient?.add(*fugafuga) …. iOS/Swift
26.
キャストした動画を制御する ● キャストしている動画をFlutter側から制御(一時停 止やシークなど)する ● Flutter側 ○
動画コントロールパネル用のUIを作る ● Plugin側 ○ 現在のキャストセッションからリモートメディアクライアン ト(remoteMediaClient)を取得して再生(play)や一時停止 (pause)、シーク(seek)を呼ぶ シーク 停止 再生 ボリューム変更
27.
Android/Kotlin private fun setMediaStreamPosition(pCall:
MethodCall, pResult: Result) { … tCastSession.remoteMediaClient.seek(tSeekTime.toLong()) … } iOS/Swift private func setMediaStreamPosition(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { … let tOptions = GCKMediaSeekOptions() // remotMediaClient.seekに渡す引数のために、ミリ秒から秒へ変換 tOptions.interval = tTime! / 1000 tCastSession?.remoteMediaClient?.seek(with: tOptions) } chrome_cast_plugin.dart Future<bool> setMediaStreamPosition(var milliseconds) async { bool _result = await _channel.invokeMethod('SetMediaStreamPosition', milliseconds); return _result; } 例)シーク
28.
応用 キャストボタンの表示バグをなくす ● Cast
Statusの状態がNOT_CONNECTEDの状態からアプリがバックグラウンドに いくと、キャストデバイスはない状態(NO_DEVICES_AVALLABLE)になる ● アプリがフォアグラウンドに戻った際に、キャストボタンが表示・非表示をし て点滅する ○ Google Playムービーなどの一部アプリでも起きている ● アプリがフォアグラウンドに戻ってきた際、数ミリ秒まってからCast Stateの 状態を取得する
29.
chrome_cast_plugin.dart class ChromeCast extends
WidgetsBindingObserver { … WidgetsBinding.instance.addObserver(this); …. @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.paused: _isDelay = true; break; default: break; } } … abstract WidgetsBindingObserver アプリのライフサイクルを監視する didChangeAppLifecycleState関数を使うために継承 didChangeAppLifecycleState AppLifecycleState state ● resumed ● inactive ● paused ● suspending のいずれかの状態を返す pausedのときに遅延を起こす WidgetsBindingObserverに登録
30.
chrome_cast_plugin.dart ... typedef OnChangeCastState =
Function(CastStatus); … class ChromeCast extends WidgetsBindingObserver { static const MethodChannel _channel = const MethodChannel('chrome_cast_plugin'); OnChangeCastState onChangeCastState; bool _isDelay = false; bool _isWaiting = false; … ChromeCast._internal() { … if (call.method == "OnChangeCastState") { CastStatus _castStatus = _getCastStateByValue(call.arguments); if (_isDelay) { if (_isWaiting == false) { _delayGetCastStatus(); } } else { onChangeCastState(_castStatus); } } }); ... } … chrome_cast_plugin.dart OnChangeCastState キャストの状態が変化したときに呼ばれるようにデリ ゲートを用意 _delayGetCastStatus 数ミリ秒待機してからキャストの状態を取得する(自作)
31.
… _delayGetCastStatus() async { _isWaiting
= true; await Future.delayed(Duration(milliseconds: _waitSeconds)); getCastStatus().then((onStatus) { if (onStatus != CastStatus.NO_DEVICES_AVAILABLE) { onChangeCastState(onStatus); _isDelay = false; } _isWaiting = false; }); } … chrome_cast_plugin.dart 数秒待機してCastStatusを取得する
Download