Slide 1

Slide 1 text

FlutterͰಈըΛ࠶ੜ͢Δ Flutter × Kotlin Multiplatform by CyberAgent #2 ઒্ ݈ଠ࿠ 2021/2/24

Slide 2

Slide 2 text

About Me ઒্ ݈ଠ࿠ FRESH LIVEɺCLͰiOSΞϓϦͷ։ൃ ΛܦͯFlutterͰ։ൃதɻFlutterྺ͸ 6ϲ݄͘Β͍ ,FFFFFO ,OTDIXBS[F Cyberagent, Inc.

Slide 3

Slide 3 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 4

Slide 4 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 5

Slide 5 text

ύοέʔδͷ঺հ • video_player • pub.dev • GitHub • flutter͕ग़͍ͯ͠Δ • iOS/Android/WebʹରԠ • iOS͸Objective-CɺAndroid͸Java

Slide 6

Slide 6 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 7

Slide 7 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 8

Slide 8 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 9

Slide 9 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 10

Slide 10 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 11

Slide 11 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 12

Slide 12 text

ϩʔΧϧϑΝΠϧɾΞηοτͷ࠶ੜ class LocalVideo extends StatefulWidget { @override _LocalVideoState createState() => _LocalVideoState(); } class _LocalVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); // _controller = VideoPlayerController.file(File('file path')); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 13

Slide 13 text

ϦϞʔτϑΝΠϧͷ࠶ੜ class RemoteVideo extends StatefulWidget { @override _RemoteVideoState createState() => _RemoteVideoState(); } class _RemoteVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 14

Slide 14 text

ϦϞʔτϑΝΠϧͷ࠶ੜ class RemoteVideo extends StatefulWidget { @override _RemoteVideoState createState() => _RemoteVideoState(); } class _RemoteVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 15

Slide 15 text

ϦϞʔτϑΝΠϧͷ࠶ੜ class RemoteVideo extends StatefulWidget { @override _RemoteVideoState createState() => _RemoteVideoState(); } class _RemoteVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8'); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return … VideoPlayer(_controller); … } }

Slide 16

Slide 16 text

ग़དྷΔ͜ͱ • ࠶ੜʗఀࢭʗγʔΫ • ϧʔϓ࠶ੜ • ϘϦϡʔϜઃఆ • ࠶ੜ଎౓ઃఆ

Slide 17

Slide 17 text

ʴЋ • ࣈນ • γʔΫόʔ • ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ

Slide 18

Slide 18 text

ʴЋ • ࣈນ • γʔΫόʔ • ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ

Slide 19

Slide 19 text

ࣈນ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context).loadString('assets/bumble_bee_captions.srt'); return SubRipCaptionFile(fileContents); } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), ); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), ], ); … } }

Slide 20

Slide 20 text

ࣈນ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context).loadString('assets/bumble_bee_captions.srt'); return SubRipCaptionFile(fileContents); } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), ); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), ], ); … } }

Slide 21

Slide 21 text

ࣈນ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context).loadString('assets/bumble_bee_captions.srt'); return SubRipCaptionFile(fileContents); } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), ); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), ], ); … } }

Slide 22

Slide 22 text

ࣈນ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context).loadString('assets/bumble_bee_captions.srt'); return SubRipCaptionFile(fileContents); } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), ); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), ], ); … } }

Slide 23

Slide 23 text

ࣈນ • ϩʔΧϧϑΝΠϧ͔ΒಡΈࠐΉ͚ͩ • ετϦʔϜ͔Βड͚औΔ৔߹͸ผ్࣮૷͕ඞཁ • SRTʹରԠ • WebVTT͸1೥͘Β͍͔͚ͯઈࢍϨϏϡʔத flutter/plugins#2878

Slide 24

Slide 24 text

ʴЋ • ࣈນ • γʔΫόʔ • ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ

Slide 25

Slide 25 text

γʔΫόʔ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ); … } }

Slide 26

Slide 26 text

γʔΫόʔ class RemoteVideo extends StatefulWidget { … } class _RemoteVideoState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network('https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); _controller.initialize(); _controller.play(); } … @override Widget build(BuildContext context) { return … Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ); … } }

Slide 27

Slide 27 text

γʔΫόʔ • ΧελϚΠζੑ௿͍͚ͲαΫοͱ͍͍ײ͡ʹͰ͖Δ

Slide 28

Slide 28 text

ʴЋ • ࣈນ • γʔΫόʔ • ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ

Slide 29

Slide 29 text

ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { _VideoAppLifeCycleObserver(this._controller); bool _wasPlayingBeforePause = false; final VideoPlayerController _controller; void initialize() { WidgetsBinding.instance!.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.paused: _wasPlayingBeforePause = _controller.value.isPlaying; _controller.pause(); break; case AppLifecycleState.resumed: if (_wasPlayingBeforePause) { _controller.play(); } break; default: } } void dispose() { WidgetsBinding.instance!.removeObserver(this); } }

Slide 30

Slide 30 text

ϥΠϑαΠΫϧʹ߹Θͤͯ࠶ੜʗఀࢭ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { _VideoAppLifeCycleObserver(this._controller); bool _wasPlayingBeforePause = false; final VideoPlayerController _controller; void initialize() { WidgetsBinding.instance!.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.paused: _wasPlayingBeforePause = _controller.value.isPlaying; _controller.pause(); break; case AppLifecycleState.resumed: if (_wasPlayingBeforePause) { _controller.play(); } break; default: } } void dispose() { WidgetsBinding.instance!.removeObserver(this); } }

Slide 31

Slide 31 text

Α͠ͳʹ΍ͬͯ͘Εͦ͏͚ͩͲ…

Slide 32

Slide 32 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 33

Slide 33 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 34

Slide 34 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 35

Slide 35 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 36

Slide 36 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 37

Slide 37 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 38

Slide 38 text

Cookieͷઃఆ • AVURLAssetੜ੒࣌ʹcookieΛ౉͍ͨ͠৔߹ • issueɾPR͸ͪΐ͍ͪΐ͍ग़ͯΔ flutter/plugins#2882 • react-nativeͰ͸Ϛʔδ͞Ε͍ͯΔ • react-native-video/react-native-video#2014 • flutter/plugins#897 (comment) • AVURLAsset(url: url, options: [AVURLAssetHTTPCookiesKey: cookies])

Slide 39

Slide 39 text

ग़དྷͳ͍͜ͱ • ը࣭ઃఆ • ϏοτϨʔτΛઃఆͰ͖ͳ͍ • ࣈນɾ෭Ի੠ • ετϦʔϜʹ৐ͬͯΔࣈນɾԻ੠ͷऔಘɺબ୒͕Ͱ͖ͳ͍ • UI • ࣗ෼Ͱ࣮૷͢Δඞཁ͕͋Δ • DRM flutter#24923 • Cookieͷઃఆ • ʢΤϥʔϋϯυϦϯάʣ

Slide 40

Slide 40 text

ΤϥʔϋϯυϦϯά class VideoPlayerController extends ValueNotifier { … } … class VideoPlayerValue { … final Duration duration; final Duration position; final Caption caption; final List buffered; final bool isPlaying; final bool isLooping; final bool isBuffering; final double volume; final double playbackSpeed; final String? errorDescription; final Size size; final bool isInitialized; bool get hasError => errorDescription != null; … }

Slide 41

Slide 41 text

ΤϥʔϋϯυϦϯά class VideoPlayerController extends ValueNotifier { … } … class VideoPlayerValue { … final Duration duration; final Duration position; final Caption caption; final List buffered; final bool isPlaying; final bool isLooping; final bool isBuffering; final double volume; final double playbackSpeed; final String? errorDescription; final Size size; final bool isInitialized; bool get hasError => errorDescription != null; … }

Slide 42

Slide 42 text

ΤϥʔϋϯυϦϯά class VideoPlayerController extends ValueNotifier { … } … class VideoPlayerValue { … final Duration duration; final Duration position; final Caption caption; final List buffered; final bool isPlaying; final bool isLooping; final bool isBuffering; final double volume; final double playbackSpeed; final String? errorDescription; final Size size; final bool isInitialized; bool get hasError => errorDescription != null; … }

Slide 43

Slide 43 text

ΤϥʔϋϯυϦϯά // iOS _eventSink([FlutterError errorWithCode:@"VideoError" message:@"Video cannot be fast-forwarded beyond 2.0x" details:nil]); _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: “ stringByAppendingString:[item.error localizedDescription]] details:nil]); // Android eventSink.error("VideoError", "Video player had error " + error, null);

Slide 44

Slide 44 text

ΤϥʔϋϯυϦϯά // iOS … AVPlayerItem* item = (AVPlayerItem*)object; switch (item.status) { case AVPlayerItemStatusFailed: if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: " stringByAppendingString:[item.error localizedDescription]] details:nil]); } break; case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; break; } …

Slide 45

Slide 45 text

ΤϥʔϋϯυϦϯά // iOS … AVPlayerItem* item = (AVPlayerItem*)object; switch (item.status) { case AVPlayerItemStatusFailed: if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: " stringByAppendingString:[item.error localizedDescription]] details:nil]); } break; case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; break; } …

Slide 46

Slide 46 text

ΤϥʔϋϯυϦϯά // Android exoPlayer.addListener( new EventListener() { … @Override public void onPlayerError(final ExoPlaybackException error) { setBuffering(false); if (eventSink != null) { eventSink.error("VideoError", "Video player had error " + error, null); } } });

Slide 47

Slide 47 text

ΤϥʔϋϯυϦϯά // Android exoPlayer.addListener( new EventListener() { … @Override public void onPlayerError(final ExoPlaybackException error) { setBuffering(false); if (eventSink != null) { eventSink.error("VideoError", "Video player had error " + error, null); } } });

Slide 48

Slide 48 text

ΤϥʔϋϯυϦϯά • ग़དྷͳ͍Θ͚Ͱ͸ͳ͍ • isBuffering౳ݟΕ͹े෼ͳ৔߹΋ • ࡉ͔͘ϋϯυϦϯά͚ͨ͠Ε͹࣮૷͢Δ͔͠ͳͦ͞͏

Slide 49

Slide 49 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 50

Slide 50 text

جຊతͳ࢓૊Έ • BasicMessageChannelͱEventChannelͷ૊Έ߹Θͤ • BasicMessageChannelɿϓϥοτϑΥʔϜͱDartؒͰ૒ํ޲ʹϝοηʔδ Λૹड৴͢ΔͨΊͷAPI • EventChannelɿϓϥοτϑΥʔϜ͔ΒDartଆʹΠϕϯτ௨஌͢ΔͨΊͷAPI video_player_platform_interface video_player EventChannel BasicMessageChannel

Slide 51

Slide 51 text

EventChannel initialized completed bufferingStart bufferingUpdate bufferingEnd video_player_platform_interface video_player Stream EventChannel

Slide 52

Slide 52 text

BasicMessageChannel video_player_platform_interface video_player getPosition(_:) send replay Future ྫ

Slide 53

Slide 53 text

BasicMessageChannel video_player_platform_interface video_player getPosition(_:) send replay Future ྫ

Slide 54

Slide 54 text

BasicMessageChannel video_player_platform_interface video_player getPosition(_:) send replay Future ྫ

Slide 55

Slide 55 text

BasicMessageChannel video_player_platform_interface video_player getPosition(_:) send replay Future ྫ

Slide 56

Slide 56 text

BasicMessageChannel video_player_platform_interface video_player getPosition(_:) send replay Future ྫ

Slide 57

Slide 57 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } }

Slide 58

Slide 58 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } } class TextureMessage { int? textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage()..textureId = pigeonMap['textureId'] as int; } }

Slide 59

Slide 59 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } } class TextureMessage { int? textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage()..textureId = pigeonMap['textureId'] as int; } }

Slide 60

Slide 60 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } } class TextureMessage { int? textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage()..textureId = pigeonMap['textureId'] as int; } }

Slide 61

Slide 61 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } } class TextureMessage { int? textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage()..textureId = pigeonMap['textureId'] as int; } }

Slide 62

Slide 62 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } }

Slide 63

Slide 63 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } } Channel͸APIຖʹ࡞੒

Slide 64

Slide 64 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } }

Slide 65

Slide 65 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } }

Slide 66

Slide 66 text

BasicMessageChannel Future position(TextureMessage arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerApi.position', StandardMessageCodec()); final Map? replyMap = await channel.send(encoded) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', details: null, ); } else if (replyMap['error'] != null) { final Map error = replyMap['error'] as Map; throw PlatformException( code: error['code'] as String, message: error['message'] as String?, details: error['details'], ); } else { return PositionMessage.decode(replyMap['result']!); } }

Slide 67

Slide 67 text

ಈըͷඳը • PlatformView

Slide 68

Slide 68 text

ಈըͷඳը • PlatformView

Slide 69

Slide 69 text

ಈըͷඳը • PlatformView • Texture Widget • https://api.flutter.dev/flutter/widgets/Texture-class.html • ϓϥοτϑΥʔϜଆ͔ΒtextureIdΛड͚औΓɺϏσΦϑϨʔϜ౸ண ࣌౳ʹࣗಈతʹόοΫΤϯυͷࢦࣔʹैͬͯ࠶ඳը͞ΕΔ • ௨ৗɺDartίʔυͷ࣮ߦ͸ؚ·Εͳ͍

Slide 70

Slide 70 text

ಈըͷඳը • Dart • iOS • Android

Slide 71

Slide 71 text

ಈըͷඳը • Dart • iOS • Android

Slide 72

Slide 72 text

Dart // VideoPlayerController @override Widget build(BuildContext context) { return _textureId == VideoPlayerController.kUninitializedTextureId ? Container() : _videoPlayerPlatform.buildView(_textureId); } // video_player_platform_interface @override Widget buildView(int textureId) { return Texture(textureId: textureId); }

Slide 73

Slide 73 text

Dart // VideoPlayerController @override Widget build(BuildContext context) { return _textureId == VideoPlayerController.kUninitializedTextureId ? Container() : _videoPlayerPlatform.buildView(_textureId); } // video_player_platform_interface @override Widget buildView(int textureId) { return Texture(textureId: textureId); }

Slide 74

Slide 74 text

Dart // VideoPlayerController @override Widget build(BuildContext context) { return _textureId == VideoPlayerController.kUninitializedTextureId ? Container() : _videoPlayerPlatform.buildView(_textureId); } // video_player_platform_interface @override Widget buildView(int textureId) { return Texture(textureId: textureId); }

Slide 75

Slide 75 text

ಈըͷඳը • Dart • iOS • Android

Slide 76

Slide 76 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture

Slide 77

Slide 77 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture

Slide 78

Slide 78 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture - (CVPixelBufferRef)copyPixelBuffer { CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { return [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; } else { return NULL; } }

Slide 79

Slide 79 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture - (CVPixelBufferRef)copyPixelBuffer { CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { return [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; } else { return NULL; } }

Slide 80

Slide 80 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture

Slide 81

Slide 81 text

iOS @protocol FlutterTexture /** Copy the contents of the texture into a `CVPixelBuffer`. */ - (CVPixelBufferRef _Nullable)copyPixelBuffer; /** * Called when the texture is unregistered. * * Called on the raster thread. */ @optional - (void)onTextureUnregistered:(NSObject*)texture; @end FlutterTexture - (void)onTextureUnregistered:(NSObject*)texture { dispatch_async(dispatch_get_main_queue(), ^{ [self dispose]; }); }

Slide 82

Slide 82 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry

Slide 83

Slide 83 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry

Slide 84

Slide 84 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry … int64_t textureId = [_registry registerTexture:player]; …

Slide 85

Slide 85 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry … int64_t textureId = [_registry registerTexture:player]; … textureId͕ฦΔ

Slide 86

Slide 86 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry

Slide 87

Slide 87 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry • FlutterʹTexture͕ߋ৽͞Εͨͱ఻͑Δ • FlutterTexture.copyPixcelBuffer()͕ݺͼ͞ΕΔ

Slide 88

Slide 88 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; ""... - (void)onDisplayLink:(CADisplayLink*)link { [_registry textureFrameAvailable:_textureId]; }

Slide 89

Slide 89 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; ""... - (void)onDisplayLink:(CADisplayLink*)link { [_registry textureFrameAvailable:_textureId]; }

Slide 90

Slide 90 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; ""... - (void)onDisplayLink:(CADisplayLink*)link { [_registry textureFrameAvailable:_textureId]; }

Slide 91

Slide 91 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry

Slide 92

Slide 92 text

iOS @protocol FlutterTextureRegistry /** * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference * that texture when calling into Flutter with channels. Textures must be registered on the * platform thread. */ - (int64_t)registerTexture:(NSObject*)texture; /** * Notifies Flutter that the content of the previously registered texture has been updated. * * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures * must be unregistered on the the platform thread. * * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end FlutterTextureRegistry - (void)dispose:(FLTTextureMessage*)input error:(FlutterError**)error { … [_registry unregisterTexture:input.textureId.intValue];

Slide 93

Slide 93 text

ಈըͷඳը • Dart • iOS • Android

Slide 94

Slide 94 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry

Slide 95

Slide 95 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry

Slide 96

Slide 96 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry ... TextureRegistry.SurfaceTextureEntry handle = flutterState.textureRegistry.createSurfaceTexture(); ...

Slide 97

Slide 97 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry

Slide 98

Slide 98 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry ... surface = new Surface(textureEntry.surfaceTexture()); exoPlayer.setVideoSurface(surface); ...

Slide 99

Slide 99 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry

Slide 100

Slide 100 text

Android public interface TextureRegistry { /** * Creates and registers a SurfaceTexture managed by the Flutter engine. * * @return A SurfaceTextureEntry. */ SurfaceTextureEntry createSurfaceTexture(); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); /** @return The identity of this SurfaceTexture. */ long id(); /** Deregisters and releases this SurfaceTexture. */ void release(); } } TextureRegistry textureId͕ฦΔ

Slide 101

Slide 101 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 102

Slide 102 text

pigeon • pub.dev • GitHub • ϓϥοτϑΥʔϜͱDartͷ઀ଓ෦෼Λࣗಈੜ੒ͯ͘͠ΕΔ

Slide 103

Slide 103 text

pigeon/messages.dart class LimitBitrateMessage { int textureId; int limitBitrate; } ... abstract class VideoPlayerApi { void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); void setLimitBitrate(LimitBitrateMessage msg); }

Slide 104

Slide 104 text

pigeon/messages.dart class LimitBitrateMessage { int textureId; int limitBitrate; } ... abstract class VideoPlayerApi { void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); void setLimitBitrate(LimitBitrateMessage msg); }

Slide 105

Slide 105 text

pigeon/messages.dart class LimitBitrateMessage { int textureId; int limitBitrate; } ... abstract class VideoPlayerApi { void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); void setLimitBitrate(LimitBitrateMessage msg); }

Slide 106

Slide 106 text

ϑΝΠϧͷੜ੒ $flutter pub run pigeon --input pigeons/messages.dart —dart_null_safety $(cd ../../../; ./script/incremental_build.sh format --travis --clang-format=clang-format-7)

Slide 107

Slide 107 text

ϑΝΠϧͷੜ੒ $flutter pub run pigeon --input pigeons/messages.dart —dart_null_safety $(cd ../../../; ./script/incremental_build.sh format --travis --clang-format=clang-format-7)

Slide 108

Slide 108 text

ϑΝΠϧͷੜ੒ $flutter pub run pigeon --input pigeons/messages.dart —dart_null_safety $(cd ../../../; ./script/incremental_build.sh format --travis --clang-format=clang-format-7)

Slide 109

Slide 109 text

ϑΝΠϧͷੜ੒ $flutter pub run pigeon --input pigeons/messages.dart —dart_null_safety $(cd ../../../; ./script/incremental_build.sh format --travis --clang-format=clang-format-7) flutter/plugin ಠࣗͷϑΥʔϚοτ

Slide 110

Slide 110 text

࣮૷͢Δ • iOS͸Objective-CɺAndroid͸JavaͰͦΕͧΕ࣮૷͢Δ

Slide 111

Slide 111 text

໨࣍ • ύοέʔδͷ঺հ • video_playerʹ͍ͭͯ • ࣮૷Λ௥ͬͯΈΔ • ػೳΛ௥Ճ͢Δ • ·ͱΊ

Slide 112

Slide 112 text

·ͱΊ • ΨνΨνͳཁ݅͡Όͳ͚Ε͹αΫοͱಋೖͰ͖ͦ͏ • Objective-CͱJava • ExoPlayerͷexampleॆ࣮ͯͨ͠

Slide 113

Slide 113 text

͋Γ͕ͱ͏͍͟͝·ͨ͠