From 96106f35e707af30c4d2a5f454d66c6e1765cbbe Mon Sep 17 00:00:00 2001 From: Hidenori Matsubayashi Date: Fri, 25 Aug 2023 22:27:20 +0000 Subject: [PATCH] video_player: add ELinuxVideoPlayer (dartPluginClass) (#80) This change fixes #78 issue of AndroidVideoPlayer being used on x64 host PC for some reason. To fix it, this change adds ELinuxVideoPlayer dart plugin class as a VideoPlayerPlatform for eLinux. Signed-off-by: Hidenori Matsubayashi --- packages/video_player/CHANGELOG.md | 3 + .../lib/src/elinux_video_player.dart | 195 ++++++++ packages/video_player/lib/src/messages.g.dart | 471 ++++++++++++++++++ .../video_player/lib/video_player_elinux.dart | 6 + packages/video_player/pubspec.yaml | 3 +- 5 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 packages/video_player/lib/src/elinux_video_player.dart create mode 100644 packages/video_player/lib/src/messages.g.dart create mode 100644 packages/video_player/lib/video_player_elinux.dart diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 8a870be..118f9a1 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.9.9 +* Add ELinuxVideoPlayer (VideoPlayerPlatform for eLinux) for flutter 3.13. + ## 0.9.8 * Update flutter/dart-sdk versions in environment for flutter 3.13.0. diff --git a/packages/video_player/lib/src/elinux_video_player.dart b/packages/video_player/lib/src/elinux_video_player.dart new file mode 100644 index 0000000..72caa5a --- /dev/null +++ b/packages/video_player/lib/src/elinux_video_player.dart @@ -0,0 +1,195 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + +import 'messages.g.dart'; + +/// An eLinux implementation of [VideoPlayerPlatform] that uses the +/// Pigeon-generated [VideoPlayerApi]. +class ELinuxVideoPlayer extends VideoPlayerPlatform { + final ELinuxVideoPlayerApi _api = ELinuxVideoPlayerApi(); + + /// Registers this class as the default instance of [PathProviderPlatform]. + static void registerWith() { + VideoPlayerPlatform.instance = ELinuxVideoPlayer(); + } + + @override + Future init() { + return _api.initialize(); + } + + @override + Future dispose(int textureId) { + return _api.dispose(TextureMessage(textureId: textureId)); + } + + @override + Future create(DataSource dataSource) async { + String? asset; + String? packageName; + String? uri; + String? formatHint; + Map httpHeaders = {}; + switch (dataSource.sourceType) { + case DataSourceType.asset: + asset = dataSource.asset; + packageName = dataSource.package; + break; + case DataSourceType.network: + uri = dataSource.uri; + formatHint = _videoFormatStringMap[dataSource.formatHint]; + httpHeaders = dataSource.httpHeaders; + break; + case DataSourceType.file: + uri = dataSource.uri; + httpHeaders = dataSource.httpHeaders; + break; + case DataSourceType.contentUri: + uri = dataSource.uri; + break; + } + final CreateMessage message = CreateMessage( + asset: asset, + packageName: packageName, + uri: uri, + httpHeaders: httpHeaders, + formatHint: formatHint, + ); + + final TextureMessage response = await _api.create(message); + return response.textureId; + } + + @override + Future setLooping(int textureId, bool looping) { + return _api.setLooping(LoopingMessage( + textureId: textureId, + isLooping: looping, + )); + } + + @override + Future play(int textureId) { + return _api.play(TextureMessage(textureId: textureId)); + } + + @override + Future pause(int textureId) { + return _api.pause(TextureMessage(textureId: textureId)); + } + + @override + Future setVolume(int textureId, double volume) { + return _api.setVolume(VolumeMessage( + textureId: textureId, + volume: volume, + )); + } + + @override + Future setPlaybackSpeed(int textureId, double speed) { + assert(speed > 0); + + return _api.setPlaybackSpeed(PlaybackSpeedMessage( + textureId: textureId, + speed: speed, + )); + } + + @override + Future seekTo(int textureId, Duration position) { + return _api.seekTo(PositionMessage( + textureId: textureId, + position: position.inMilliseconds, + )); + } + + @override + Future getPosition(int textureId) async { + final PositionMessage response = + await _api.position(TextureMessage(textureId: textureId)); + return Duration(milliseconds: response.position); + } + + @override + Stream videoEventsFor(int textureId) { + return _eventChannelFor(textureId) + .receiveBroadcastStream() + .map((dynamic event) { + final Map map = event as Map; + switch (map['event']) { + case 'initialized': + return VideoEvent( + eventType: VideoEventType.initialized, + duration: Duration(milliseconds: map['duration'] as int), + size: Size((map['width'] as num?)?.toDouble() ?? 0.0, + (map['height'] as num?)?.toDouble() ?? 0.0), + rotationCorrection: map['rotationCorrection'] as int? ?? 0, + ); + case 'completed': + return VideoEvent( + eventType: VideoEventType.completed, + ); + case 'bufferingUpdate': + final List values = map['values'] as List; + + return VideoEvent( + buffered: values.map(_toDurationRange).toList(), + eventType: VideoEventType.bufferingUpdate, + ); + case 'bufferingStart': + return VideoEvent(eventType: VideoEventType.bufferingStart); + case 'bufferingEnd': + return VideoEvent(eventType: VideoEventType.bufferingEnd); +/* TODO: need implementation + case 'isPlayingStateUpdate': + return VideoEvent( + eventType: VideoEventType.isPlayingStateUpdate, + isPlaying: map['isPlaying'] as bool, + ); +*/ + default: + return VideoEvent(eventType: VideoEventType.unknown); + } + }); + } + + @override + Widget buildView(int textureId) { + return Texture(textureId: textureId); + } + + @override + Future setMixWithOthers(bool mixWithOthers) { + return _api + .setMixWithOthers(MixWithOthersMessage(mixWithOthers: mixWithOthers)); + } + + EventChannel _eventChannelFor(int textureId) { + return EventChannel('flutter.io/videoPlayer/videoEvents$textureId'); + } + + static const Map _videoFormatStringMap = + { + VideoFormat.ss: 'ss', + VideoFormat.hls: 'hls', + VideoFormat.dash: 'dash', + VideoFormat.other: 'other', + }; + + DurationRange _toDurationRange(dynamic value) { + final List pair = value as List; + return DurationRange( + Duration(milliseconds: pair[0] as int), + Duration(milliseconds: pair[1] as int), + ); + } +} diff --git a/packages/video_player/lib/src/messages.g.dart b/packages/video_player/lib/src/messages.g.dart new file mode 100644 index 0000000..1b62cdc --- /dev/null +++ b/packages/video_player/lib/src/messages.g.dart @@ -0,0 +1,471 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v9.2.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class TextureMessage { + TextureMessage({ + required this.textureId, + }); + + 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, + ); + } +} + +class CreateMessage { + CreateMessage({ + this.asset, + this.uri, + this.packageName, + this.formatHint, + required this.httpHeaders, + }); + + String? asset; + String? uri; + String? packageName; + String? formatHint; + Map httpHeaders; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['asset'] = asset; + pigeonMap['uri'] = uri; + pigeonMap['packageName'] = packageName; + pigeonMap['formatHint'] = formatHint; + pigeonMap['httpHeaders'] = httpHeaders; + return pigeonMap; + } + + static CreateMessage decode(Object message) { + final Map pigeonMap = message as Map; + return CreateMessage( + asset: pigeonMap['asset'] as String?, + uri: pigeonMap['uri'] as String?, + packageName: pigeonMap['packageName'] as String?, + formatHint: pigeonMap['formatHint'] as String?, + httpHeaders: pigeonMap['httpHeaders'] as Map, + ); + } +} + +class LoopingMessage { + LoopingMessage({ + required this.textureId, + required this.isLooping, + }); + + int textureId; + bool isLooping; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['textureId'] = textureId; + pigeonMap['isLooping'] = isLooping; + return pigeonMap; + } + + static LoopingMessage decode(Object message) { + final Map pigeonMap = message as Map; + return LoopingMessage( + textureId: pigeonMap['textureId'] as int, + isLooping: pigeonMap['isLooping'] as bool, + ); + } +} + +class VolumeMessage { + VolumeMessage({ + required this.textureId, + required this.volume, + }); + + int textureId; + double volume; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['textureId'] = textureId; + pigeonMap['volume'] = volume; + return pigeonMap; + } + + static VolumeMessage decode(Object message) { + final Map pigeonMap = message as Map; + return VolumeMessage( + textureId: pigeonMap['textureId'] as int, + volume: pigeonMap['volume'] as double, + ); + } +} + +class PlaybackSpeedMessage { + PlaybackSpeedMessage({ + required this.textureId, + required this.speed, + }); + + int textureId; + double speed; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['textureId'] = textureId; + pigeonMap['speed'] = speed; + return pigeonMap; + } + + static PlaybackSpeedMessage decode(Object message) { + final Map pigeonMap = message as Map; + return PlaybackSpeedMessage( + textureId: pigeonMap['textureId'] as int, + speed: pigeonMap['speed'] as double, + ); + } +} + +class PositionMessage { + PositionMessage({ + required this.textureId, + required this.position, + }); + + int textureId; + int position; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['textureId'] = textureId; + pigeonMap['position'] = position; + return pigeonMap; + } + + static PositionMessage decode(Object message) { + final Map pigeonMap = message as Map; + return PositionMessage( + textureId: pigeonMap['textureId'] as int, + position: pigeonMap['position'] as int, + ); + } +} + +class MixWithOthersMessage { + MixWithOthersMessage({ + required this.mixWithOthers, + }); + + bool mixWithOthers; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['mixWithOthers'] = mixWithOthers; + return pigeonMap; + } + + static MixWithOthersMessage decode(Object message) { + final Map pigeonMap = message as Map; + return MixWithOthersMessage( + mixWithOthers: pigeonMap['mixWithOthers'] as bool, + ); + } +} + +/// [VideoPlayerApi] in +class ELinuxVideoPlayerApi { + Future initialize() async { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.initialize', StandardMessageCodec()); + final Map? replyMap = + await channel.send(null) 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 { + // noop + } + } + + Future create(CreateMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.create', 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 TextureMessage.decode(replyMap['result']!); + } + } + + Future dispose(TextureMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.dispose', 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 { + // noop + } + } + + Future setLooping(LoopingMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.setLooping', 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 { + // noop + } + } + + Future setVolume(VolumeMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.setVolume', 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 { + // noop + } + } + + Future setPlaybackSpeed(PlaybackSpeedMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.setPlaybackSpeed', + 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 { + // noop + } + } + + Future play(TextureMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.play', 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 { + // noop + } + } + + 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']!); + } + } + + Future seekTo(PositionMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.seekTo', 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 { + // noop + } + } + + Future pause(TextureMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.pause', 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 { + // noop + } + } + + Future setMixWithOthers(MixWithOthersMessage arg) async { + final Object encoded = arg.encode(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerApi.setMixWithOthers', + 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 { + // noop + } + } +} diff --git a/packages/video_player/lib/video_player_elinux.dart b/packages/video_player/lib/video_player_elinux.dart new file mode 100644 index 0000000..18d9a12 --- /dev/null +++ b/packages/video_player/lib/video_player_elinux.dart @@ -0,0 +1,6 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/elinux_video_player.dart'; diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 698d4b1..643f07b 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -1,6 +1,6 @@ name: video_player_elinux description: Flutter plugin for displaying inline video with other Flutter widgets on Embedded Linux. -version: 0.9.8 +version: 0.9.9 homepage: https://github.com/sony/flutter-elinux-plugins repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/video_player @@ -18,3 +18,4 @@ flutter: platforms: elinux: pluginClass: VideoPlayerElinuxPlugin + dartPluginClass: ELinuxVideoPlayer