SlideShare a Scribd company logo
Cast SDK for Flutter
山田 幸司
koji.yamada@gree.net
グリー株式会社
開発本部 / インフラストラクチャ部 / ディベロップメント
オペレーションズグループ / サービスディベロップメント
チーム 所属
業務内容
VRアプリフロントエンド担当
最近のお仕事はFlutter / Unity / Unreal Engine 4など
アジェンダ
● Chromecastについて
○ Chromecastとは?
○ Cast SDKについて
● Cast SDK for Flutterを作る
○ Flutterでプラグインを作る準備やインストール方法など
○ 構成から実装のおおまかな流れ
○ 実装方法などについて
Chromecast
● Googleが開発・販売する小型のデバイス
● HDMI端子に接続/Wi-Fiを介してスマートフォンやタブレットなどで再生して
いる動画、写真、ウェブサイトなどをディスプレイに表示出来るデバイス
● ミラーリングとはやや違ってスマートフォンをコントローラとして扱い動画
の再生や停止などもできる
● 値段は5,000円程度(4K対応のUltraは10,000円程度)
● アプリで利用するにはCast SDKを組み込む必要がある
※↑アプリが対応しているかつ周辺にChromecast
がある場合はこのアイコンが表示される
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
Cast SDK for Flutter
● 今日の話
○ Flutter用のChromecast Pluginを作る話
○ ChromecastのAndroid/iOS両方に対応するアプリをさく
っと作れる
● 実装するもの
○ 送信側(Sender)と受信側(Receiver)を作る必要があ
りますが、今回はSenderアプリのみについて
○ 動画コントロールパネルやキャストボタンなどのUI部分
はFlutterで実装
○ 動画をChromecastにキャスト/再生と停止ができる
準備
● 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
構成
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とのつなぎ
プラグイン側
Pluginで実装すること
1. Cast Contextの初期化
2. Cast Dialogの表示
3. Cast Buttonの表示
4. リスナーの登録
5. 動画をキャストする
6. キャストした動画を制御する
Cast Contextの初期化
● Chromecast の機能を使うために必要
● Android
○ OptionsProviderインタフェースを実装したクラスの作成
○ レシーバーIdのCastOptionsのインスタンスを作る
○ CastContext.getSharedInstance(Activity)で初期化
● iOS
○ レシーバーIdのGCKCastOptionのインスタンスを作る
○ GCKCastContext.setSharedInstanceWith(GCKCastOption)で初期化
● 初期化タイミング
○ pluginのregisterWith / register
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
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
Cast Dialogの表示
● キャストしてないとき
○ キャスト可能なデバイス一覧を表示
● キャストしてるとき
○ メディア情報、音量などを表示
● Android(少々手間がかかる)
○ 現在キャスト中かどうかを判定して出し分けが必要
○ 現在のキャストセッションがあれば
MediaRouteControllerDialog
○ 無い場合はMediaRouteChooserDialogを呼ぶ
● iOS
○ presentCastDialogを呼ぶだけ
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
動画をキャストする
● 動画をキャストするために必要な情報をFlutterからPlugin側に渡す
● 手順
○ [1] キャストするための必要なメタデータ(Metadata)を作成するためのパ
ラメータ
■ タイトル、キャストダイアログに出す画像など
○ [2] 作成したメタデータからメディア情報(MediaInfo)を作成するためのパ
ラメータ
■ 動画のコンテンツタイプ(mp4など)、レシーバーに渡す独自のJsonデータなど
○ [3] 必要に応じて読み込み時のオプションを作成するためのパラメータ
■ 再生位置、自動再生するかどうかなど
○ [4] 動画をデバイスにキャスト
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,
}) :
...
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)
}
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
実演(動画)
● 仕様
○ 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
ここから先のスライドは補足
補足 iOS/Xcodeで開発する場合の注意
● Xcode10以上、iOS 12以降をターゲットにしている場合
● Access Wifi InformationをONにする
Cast Buttonの表示
● キャスト可能なデバイスがあるときはアイコンを表示、接続中は接続中のア
イコン、キャスト可能なデバイスがない場合は非表示に切り替える
● Cast StatusをAndroid/iOSのプラグイン側から取得
● Cast Statusの状態をもとにFlutter側でアイコンの表示・非表示を切り替える
キャストしてない キャスト中
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
リスナーの登録
● SessionManager Listener
○ アプリ全体のキャストセッションを管理
○ SessionManagerに登録する
■ アプリがセッションを開始したとき
■ アプリがセッションとの接続を停止したとき など…
● RemoteMediaClient Listener
○ アプリと現在キャストしているデバイス(Chromecast)のメディアのセッションを管理
○ CastSessionに登録する
■ 動画などをキャストをしたとき
■ キャストした動画に変更があったとき など…
● Flutter側の実装は必要なし
○ ※リスナーの中身の実装は今回は省略
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
キャストした動画を制御する
● キャストしている動画をFlutter側から制御(一時停
止やシークなど)する
● Flutter側
○ 動画コントロールパネル用のUIを作る
● Plugin側
○ 現在のキャストセッションからリモートメディアクライアン
ト(remoteMediaClient)を取得して再生(play)や一時停止
(pause)、シーク(seek)を呼ぶ
シーク
停止
再生
ボリューム変更
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;
}
例)シーク
応用 キャストボタンの表示バグをなくす
● Cast Statusの状態がNOT_CONNECTEDの状態からアプリがバックグラウンドに
いくと、キャストデバイスはない状態(NO_DEVICES_AVALLABLE)になる
● アプリがフォアグラウンドに戻った際に、キャストボタンが表示・非表示をし
て点滅する
○ Google Playムービーなどの一部アプリでも起きている
● アプリがフォアグラウンドに戻ってきた際、数ミリ秒まってからCast Stateの
状態を取得する
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に登録
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
数ミリ秒待機してからキャストの状態を取得する(自作)
…
_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を取得する

More Related Content

What's hot

アプリ起動時間高速化 ~推測するな、計測せよ~
アプリ起動時間高速化 ~推測するな、計測せよ~アプリ起動時間高速化 ~推測するな、計測せよ~
アプリ起動時間高速化 ~推測するな、計測せよ~
gree_tech
 
オススメの標準・準標準パッケージ20選
オススメの標準・準標準パッケージ20選オススメの標準・準標準パッケージ20選
オススメの標準・準標準パッケージ20選
Takuya Ueda
 
究極のゲーム用通信プロトコルを探せ!
究極のゲーム用通信プロトコルを探せ!究極のゲーム用通信プロトコルを探せ!
究極のゲーム用通信プロトコルを探せ!
Ryosuke Otsuya
 
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
NTT DATA Technology & Innovation
 
GoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホンGoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホン
Akihiko Horiuchi
 
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
NTT DATA Technology & Innovation
 
What is Enterprise Agile
What is Enterprise Agile What is Enterprise Agile
What is Enterprise Agile
Kenji Hiranabe
 
ネットワーク ゲームにおけるTCPとUDPの使い分け
ネットワーク ゲームにおけるTCPとUDPの使い分けネットワーク ゲームにおけるTCPとUDPの使い分け
ネットワーク ゲームにおけるTCPとUDPの使い分け
モノビット エンジン
 
MediaRecorder と WebM で、オレオレ Live Streaming
MediaRecorder と WebM で、オレオレ Live StreamingMediaRecorder と WebM で、オレオレ Live Streaming
MediaRecorder と WebM で、オレオレ Live Streaming
mganeko
 
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
Hironobu Isoda
 
組織にテストを書く文化を根付かせる戦略と戦術
組織にテストを書く文化を根付かせる戦略と戦術組織にテストを書く文化を根付かせる戦略と戦術
組織にテストを書く文化を根付かせる戦略と戦術
Takuto Wada
 
リクルートのWebサービスを支える共通インフラ「RAFTEL」
リクルートのWebサービスを支える共通インフラ「RAFTEL」リクルートのWebサービスを支える共通インフラ「RAFTEL」
リクルートのWebサービスを支える共通インフラ「RAFTEL」
Recruit Technologies
 
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
Gemdrops Inc.
 
BLEACH -Brave Souls- 3DUI演出の実装事例
BLEACH -Brave Souls- 3DUI演出の実装事例BLEACH -Brave Souls- 3DUI演出の実装事例
BLEACH -Brave Souls- 3DUI演出の実装事例
KLab Inc. / Tech
 
マイクロアドのアドテクを支える技術
マイクロアドのアドテクを支える技術マイクロアドのアドテクを支える技術
マイクロアドのアドテクを支える技術
MicroAd, Inc.(Engineer)
 
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
Hironobu Isoda
 
明治大学理工学部 特別講義 AI on Azure
明治大学理工学部 特別講義 AI on Azure明治大学理工学部 特別講義 AI on Azure
明治大学理工学部 特別講義 AI on Azure
Daiyu Hatakeyama
 
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
Game Tools & Middleware Forum
 
Visual Studio Code で C# でのアプリ開発
Visual Studio Code で C# でのアプリ開発Visual Studio Code で C# でのアプリ開発
Visual Studio Code で C# でのアプリ開発
m ishizaki
 
時代遅れと言われようとMdaフレームワークの紹介
時代遅れと言われようとMdaフレームワークの紹介時代遅れと言われようとMdaフレームワークの紹介
時代遅れと言われようとMdaフレームワークの紹介
MaxNeetGames
 

What's hot (20)

アプリ起動時間高速化 ~推測するな、計測せよ~
アプリ起動時間高速化 ~推測するな、計測せよ~アプリ起動時間高速化 ~推測するな、計測せよ~
アプリ起動時間高速化 ~推測するな、計測せよ~
 
オススメの標準・準標準パッケージ20選
オススメの標準・準標準パッケージ20選オススメの標準・準標準パッケージ20選
オススメの標準・準標準パッケージ20選
 
究極のゲーム用通信プロトコルを探せ!
究極のゲーム用通信プロトコルを探せ!究極のゲーム用通信プロトコルを探せ!
究極のゲーム用通信プロトコルを探せ!
 
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
ひと漕ぎで二度おいしい!? Flutterを使ったモバイルアプリ開発への期待と実態と付き合い方(NTTデータ テクノロジーカンファレンス 2020 発表資料)
 
GoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホンGoによるWebアプリ開発のキホン
GoによるWebアプリ開発のキホン
 
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
 
What is Enterprise Agile
What is Enterprise Agile What is Enterprise Agile
What is Enterprise Agile
 
ネットワーク ゲームにおけるTCPとUDPの使い分け
ネットワーク ゲームにおけるTCPとUDPの使い分けネットワーク ゲームにおけるTCPとUDPの使い分け
ネットワーク ゲームにおけるTCPとUDPの使い分け
 
MediaRecorder と WebM で、オレオレ Live Streaming
MediaRecorder と WebM で、オレオレ Live StreamingMediaRecorder と WebM で、オレオレ Live Streaming
MediaRecorder と WebM で、オレオレ Live Streaming
 
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
200,000 Req/sec をさばく広告入札システムを支えるパフォーマンスチューニング術 #jjug_ccc #ccc_g6
 
組織にテストを書く文化を根付かせる戦略と戦術
組織にテストを書く文化を根付かせる戦略と戦術組織にテストを書く文化を根付かせる戦略と戦術
組織にテストを書く文化を根付かせる戦略と戦術
 
リクルートのWebサービスを支える共通インフラ「RAFTEL」
リクルートのWebサービスを支える共通インフラ「RAFTEL」リクルートのWebサービスを支える共通インフラ「RAFTEL」
リクルートのWebサービスを支える共通インフラ「RAFTEL」
 
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
PlayStation®4向けARPGのUnity開発事例 最適化と効率化の秘密
 
BLEACH -Brave Souls- 3DUI演出の実装事例
BLEACH -Brave Souls- 3DUI演出の実装事例BLEACH -Brave Souls- 3DUI演出の実装事例
BLEACH -Brave Souls- 3DUI演出の実装事例
 
マイクロアドのアドテクを支える技術
マイクロアドのアドテクを支える技術マイクロアドのアドテクを支える技術
マイクロアドのアドテクを支える技術
 
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
オンライン広告入札システムとZGC ( JJUG CCC 2021 Spring )
 
明治大学理工学部 特別講義 AI on Azure
明治大学理工学部 特別講義 AI on Azure明治大学理工学部 特別講義 AI on Azure
明治大学理工学部 特別講義 AI on Azure
 
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
Game Server Services ではじめる サーバー開発運用しないゲーム開発 /GTMF2019
 
Visual Studio Code で C# でのアプリ開発
Visual Studio Code で C# でのアプリ開発Visual Studio Code で C# でのアプリ開発
Visual Studio Code で C# でのアプリ開発
 
時代遅れと言われようとMdaフレームワークの紹介
時代遅れと言われようとMdaフレームワークの紹介時代遅れと言われようとMdaフレームワークの紹介
時代遅れと言われようとMdaフレームワークの紹介
 

Similar to Cast SDK for Flutter

QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会Jumpei Ogawa
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaandroid sola
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium MobileNaoya Ito
 
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Shotaro Suzuki
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
Yoshitaka Seo
 
Redmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspointsRedmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspoints
Shinji Tamura
 
WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会
Yusuke Naka
 
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
de:code 2017
 
Android bluetooth
Android bluetoothAndroid bluetooth
Android bluetooth
Masahiro Hidaka
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
Hiroshi Miura
 
FlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解するFlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解する
KeisukeKiriyama
 
VRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発についてVRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発について
yashinut
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
Hiroshi Miura
 
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTERMRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
エピック・ゲームズ・ジャパン Epic Games Japan
 
.NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版).NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版)
Yoshitaka Seo
 
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Takahiro Miyaura
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTC
yoshikawa_t
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as Code
Takuhiro Yoshida
 
20120118 titanium
20120118 titanium20120118 titanium
20120118 titanium
Hiroshi Oyamada
 
Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!
Yamato Honda
 

Similar to Cast SDK for Flutter (20)

QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsola
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium Mobile
 
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
 
Redmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspointsRedmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspoints
 
WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会
 
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
 
Android bluetooth
Android bluetoothAndroid bluetooth
Android bluetooth
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
 
FlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解するFlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解する
 
VRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発についてVRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発について
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
 
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTERMRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
 
.NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版).NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版)
 
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTC
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as Code
 
20120118 titanium
20120118 titanium20120118 titanium
20120118 titanium
 
Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!Unityのポストエフェクトで遊ぶ!
Unityのポストエフェクトで遊ぶ!
 

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
  • 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を取得する