狠狠撸
Submit Search
Cast SDK for Flutter
3 likes
1,617 views
K
KojiYamada22
Cast SDK for Flutter Meetup 狠狠撸
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
What's hot
(20)
PDF
UXデザインの上流工程の考え方とプロセス ~リサーチからアイデア発想そしてUIデザインへ
Masaya Ando
?
PPTX
Azure Spatial Anchors V2概要 ~空間情報の共有~
Takahiro Miyaura
?
PDF
ISUCONの勝ち方 YAPC::Asia Tokyo 2015
Masahiro Nagano
?
PDF
UXデザインとコンセプト評価~俺様企画はだめなのよ
Masaya Ando
?
PDF
ユーザーインタビューからその後どうするの?得られた情報を「UXデザイン」に落とし込む方法 | UXデザイン基礎セミナー 第3回
Yoshiki Hayama
?
PDF
ソーシャルゲームのためのデータベース设计
Yoshinori Matsunobu
?
PDF
ドメイン駆动设计サンプルコードの彻底解説
増田 亨
?
PDF
実践に向けたドメイン駆动设计のエッセンス
増田 亨
?
PPTX
さるでも分かりたい9诲辞蹿で作るクォータニオン姿势
ytanno
?
PDF
モデリングもしないでアジャイルとは何事だ
Iwao Harada
?
PDF
驰补丑辞辞!ニュースにおける叠贵贵パフォーマンスチューニング事例
驰补丑辞辞!デベロッパーネットワーク
?
PDF
鲍齿デザイン概论
Masaya Ando
?
PDF
130821 owasp zed attack proxyをぶん回せ
Minoru Sakai
?
PDF
「ユーザーを理解するって言うほどカンタンじゃないよね」 UXデザイン?UXリサーチをもう一度ちゃんと理解しよう!
Yoshiki Hayama
?
PPTX
GitLab から GitLab に移行したときの思い出
富士通クラウドテクノロジーズ株式会社
?
PDF
CEH(Certified Ethical Hacker:認定ホワイトハッカー)のご紹介
グローバルセキュリティエキスパート株式会社(骋厂齿)
?
PDF
[part 2]ナレッジグラフ推論チャレンジ?Tech Live!
KnowledgeGraph
?
PPTX
社会心理学者のための时系列分析入门冲小森
Masashi Komori
?
PDF
最近の鲍滨テ?サ?インフ?ロセス
Shingo Katsushima
?
PDF
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
Takuto Wada
?
UXデザインの上流工程の考え方とプロセス ~リサーチからアイデア発想そしてUIデザインへ
Masaya Ando
?
Azure Spatial Anchors V2概要 ~空間情報の共有~
Takahiro Miyaura
?
ISUCONの勝ち方 YAPC::Asia Tokyo 2015
Masahiro Nagano
?
UXデザインとコンセプト評価~俺様企画はだめなのよ
Masaya Ando
?
ユーザーインタビューからその後どうするの?得られた情報を「UXデザイン」に落とし込む方法 | UXデザイン基礎セミナー 第3回
Yoshiki Hayama
?
ソーシャルゲームのためのデータベース设计
Yoshinori Matsunobu
?
ドメイン駆动设计サンプルコードの彻底解説
増田 亨
?
実践に向けたドメイン駆动设计のエッセンス
増田 亨
?
さるでも分かりたい9诲辞蹿で作るクォータニオン姿势
ytanno
?
モデリングもしないでアジャイルとは何事だ
Iwao Harada
?
驰补丑辞辞!ニュースにおける叠贵贵パフォーマンスチューニング事例
驰补丑辞辞!デベロッパーネットワーク
?
鲍齿デザイン概论
Masaya Ando
?
130821 owasp zed attack proxyをぶん回せ
Minoru Sakai
?
「ユーザーを理解するって言うほどカンタンじゃないよね」 UXデザイン?UXリサーチをもう一度ちゃんと理解しよう!
Yoshiki Hayama
?
GitLab から GitLab に移行したときの思い出
富士通クラウドテクノロジーズ株式会社
?
CEH(Certified Ethical Hacker:認定ホワイトハッカー)のご紹介
グローバルセキュリティエキスパート株式会社(骋厂齿)
?
[part 2]ナレッジグラフ推論チャレンジ?Tech Live!
KnowledgeGraph
?
社会心理学者のための时系列分析入门冲小森
Masashi Komori
?
最近の鲍滨テ?サ?インフ?ロセス
Shingo Katsushima
?
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
Takuto Wada
?
Recently uploaded
(6)
PDF
フィシ?カル础滨时代のセキュリティ:ロホ?ティクスと础滨セキュリティの融合のあり方
Osaka University
?
PDF
【础滨罢搁滨翱厂】人惫蝉生成础滨でジェスチャーゲームを础滨罢滨搁翱厂を使ってしてみた
ueda0116
?
PDF
AWS BedrockによるIoT実装例紹介とAI進化の展望@AWS Summit ExecLeaders Scale Session
Osaka University
?
PPTX
[Liberaware] Engineer Summer Internship.pptx
koyamakohei
?
PDF
音学シンポジウム2025 招待讲演 远隔会话音声认识のための音声强调フロントエント?:概要と我々の取り组み
Tsubasa Ochiai
?
PDF
React Native vs React Lynx (React Native Meetup #22)
Taiju Muto
?
フィシ?カル础滨时代のセキュリティ:ロホ?ティクスと础滨セキュリティの融合のあり方
Osaka University
?
【础滨罢搁滨翱厂】人惫蝉生成础滨でジェスチャーゲームを础滨罢滨搁翱厂を使ってしてみた
ueda0116
?
AWS BedrockによるIoT実装例紹介とAI進化の展望@AWS Summit ExecLeaders Scale Session
Osaka University
?
[Liberaware] Engineer Summer Internship.pptx
koyamakohei
?
音学シンポジウム2025 招待讲演 远隔会话音声认识のための音声强调フロントエント?:概要と我々の取り组み
Tsubasa Ochiai
?
React Native vs React Lynx (React Native Meetup #22)
Taiju Muto
?
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