From 0812a8f7d7b9e817398a758874ebdede1d9936f6 Mon Sep 17 00:00:00 2001 From: hyue7 <111051566+hyue7@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:01:21 +0800 Subject: [PATCH] [video_player_avplay] Add getDuration Api (#650) --- packages/video_player_avplay/CHANGELOG.md | 3 + packages/video_player_avplay/README.md | 2 +- .../integration_test/video_player_test.dart | 4 +- .../lib/src/messages.g.dart | 96 ++++++++++--- .../lib/src/video_player_tizen.dart | 13 +- .../video_player_avplay/lib/video_player.dart | 72 +++++++--- .../lib/video_player_platform_interface.dart | 7 +- .../video_player_avplay/pigeons/messages.dart | 7 + packages/video_player_avplay/pubspec.yaml | 2 +- .../tizen/src/drm_manager.cc | 1 + .../tizen/src/media_player.cc | 63 ++++++++- .../tizen/src/media_player.h | 4 +- .../tizen/src/media_player_proxy.cc | 21 +++ .../tizen/src/media_player_proxy.h | 40 ++++++ .../video_player_avplay/tizen/src/messages.cc | 132 +++++++++++++++--- .../video_player_avplay/tizen/src/messages.h | 27 ++++ .../tizen/src/plus_player.cc | 89 +++++------- .../tizen/src/plus_player.h | 4 +- .../tizen/src/video_player.cc | 8 +- .../tizen/src/video_player.h | 2 +- .../tizen/src/video_player_tizen_plugin.cc | 16 +++ 21 files changed, 487 insertions(+), 126 deletions(-) diff --git a/packages/video_player_avplay/CHANGELOG.md b/packages/video_player_avplay/CHANGELOG.md index 381fa139e..4998c2bf5 100644 --- a/packages/video_player_avplay/CHANGELOG.md +++ b/packages/video_player_avplay/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.2.0 +* Add get duration API for live stream. + ## 0.1.3 * Fix issue of not display video when start play. diff --git a/packages/video_player_avplay/README.md b/packages/video_player_avplay/README.md index 08690f04f..37c9a2e82 100644 --- a/packages/video_player_avplay/README.md +++ b/packages/video_player_avplay/README.md @@ -12,7 +12,7 @@ To use this package, add `video_player_avplay` as a dependency in your `pubspec. ```yaml dependencies: - video_player_avplay: ^0.1.3 + video_player_avplay: ^0.2.0 ``` Then you can import `video_player_avplay` in your Dart code: diff --git a/packages/video_player_avplay/example/integration_test/video_player_test.dart b/packages/video_player_avplay/example/integration_test/video_player_test.dart index 4167278ae..6bcec5379 100644 --- a/packages/video_player_avplay/example/integration_test/video_player_test.dart +++ b/packages/video_player_avplay/example/integration_test/video_player_test.dart @@ -125,7 +125,7 @@ void main() { // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); final Duration timeBeforeEnd = - controller.value.duration - const Duration(milliseconds: 500); + controller.value.duration.end - const Duration(milliseconds: 500); await controller.seekTo(timeBeforeEnd); await controller.play(); await tester.pumpAndSettle(_playDuration); @@ -148,7 +148,7 @@ void main() { // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); await controller.seekTo( - controller.value.duration - const Duration(milliseconds: 500)); + controller.value.duration.end - const Duration(milliseconds: 500)); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, false); diff --git a/packages/video_player_avplay/lib/src/messages.g.dart b/packages/video_player_avplay/lib/src/messages.g.dart index ecb5d8716..e117a2742 100644 --- a/packages/video_player_avplay/lib/src/messages.g.dart +++ b/packages/video_player_avplay/lib/src/messages.g.dart @@ -332,6 +332,32 @@ class GeometryMessage { } } +class DurationMessage { + DurationMessage({ + required this.playerId, + this.durationRange, + }); + + int playerId; + + List? durationRange; + + Object encode() { + return [ + playerId, + durationRange, + ]; + } + + static DurationMessage decode(Object result) { + result as List; + return DurationMessage( + playerId: result[0]! as int, + durationRange: (result[1] as List?)?.cast(), + ); + } +} + class _VideoPlayerAvplayApiCodec extends StandardMessageCodec { const _VideoPlayerAvplayApiCodec(); @override @@ -339,36 +365,39 @@ class _VideoPlayerAvplayApiCodec extends StandardMessageCodec { if (value is CreateMessage) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is GeometryMessage) { + } else if (value is DurationMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is LoopingMessage) { + } else if (value is GeometryMessage) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is MixWithOthersMessage) { + } else if (value is LoopingMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is PlaybackSpeedMessage) { + } else if (value is MixWithOthersMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is PlayerMessage) { + } else if (value is PlaybackSpeedMessage) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is PositionMessage) { + } else if (value is PlayerMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SelectedTracksMessage) { + } else if (value is PositionMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is TrackMessage) { + } else if (value is SelectedTracksMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is TrackTypeMessage) { + } else if (value is TrackMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is VolumeMessage) { + } else if (value is TrackTypeMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); + } else if (value is VolumeMessage) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -380,24 +409,26 @@ class _VideoPlayerAvplayApiCodec extends StandardMessageCodec { case 128: return CreateMessage.decode(readValue(buffer)!); case 129: - return GeometryMessage.decode(readValue(buffer)!); + return DurationMessage.decode(readValue(buffer)!); case 130: - return LoopingMessage.decode(readValue(buffer)!); + return GeometryMessage.decode(readValue(buffer)!); case 131: - return MixWithOthersMessage.decode(readValue(buffer)!); + return LoopingMessage.decode(readValue(buffer)!); case 132: - return PlaybackSpeedMessage.decode(readValue(buffer)!); + return MixWithOthersMessage.decode(readValue(buffer)!); case 133: - return PlayerMessage.decode(readValue(buffer)!); + return PlaybackSpeedMessage.decode(readValue(buffer)!); case 134: - return PositionMessage.decode(readValue(buffer)!); + return PlayerMessage.decode(readValue(buffer)!); case 135: - return SelectedTracksMessage.decode(readValue(buffer)!); + return PositionMessage.decode(readValue(buffer)!); case 136: - return TrackMessage.decode(readValue(buffer)!); + return SelectedTracksMessage.decode(readValue(buffer)!); case 137: - return TrackTypeMessage.decode(readValue(buffer)!); + return TrackMessage.decode(readValue(buffer)!); case 138: + return TrackTypeMessage.decode(readValue(buffer)!); + case 139: return VolumeMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -708,6 +739,33 @@ class VideoPlayerAvplayApi { } } + Future duration(PlayerMessage arg_msg) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.VideoPlayerAvplayApi.duration', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_msg]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as DurationMessage?)!; + } + } + Future seekTo(PositionMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.VideoPlayerAvplayApi.seekTo', codec, diff --git a/packages/video_player_avplay/lib/src/video_player_tizen.dart b/packages/video_player_avplay/lib/src/video_player_tizen.dart index 11526dd5e..36e1f1711 100644 --- a/packages/video_player_avplay/lib/src/video_player_tizen.dart +++ b/packages/video_player_avplay/lib/src/video_player_tizen.dart @@ -181,6 +181,14 @@ class VideoPlayerTizen extends VideoPlayerPlatform { )); } + @override + Future getDuration(int playerId) async { + final DurationMessage message = + await _api.duration(PlayerMessage(playerId: playerId)); + return DurationRange(Duration(milliseconds: message.durationRange?[0] ?? 0), + Duration(milliseconds: message.durationRange?[1] ?? 0)); + } + @override Future getPosition(int playerId) async { final PositionMessage response = @@ -196,9 +204,12 @@ class VideoPlayerTizen extends VideoPlayerPlatform { final Map map = event as Map; switch (map['event']) { case 'initialized': + final List? durationVal = map['duration'] as List?; return VideoEvent( eventType: VideoEventType.initialized, - duration: Duration(milliseconds: map['duration']! as int), + duration: DurationRange( + Duration(milliseconds: durationVal?[0] as int), + Duration(milliseconds: durationVal?[1] as int)), size: Size((map['width'] as num?)?.toDouble() ?? 0.0, (map['height'] as num?)?.toDouble() ?? 0.0), ); diff --git a/packages/video_player_avplay/lib/video_player.dart b/packages/video_player_avplay/lib/video_player.dart index f41e1f494..c8a020a6d 100644 --- a/packages/video_player_avplay/lib/video_player.dart +++ b/packages/video_player_avplay/lib/video_player.dart @@ -57,12 +57,14 @@ class VideoPlayerValue { /// Returns an instance for a video that hasn't been loaded. VideoPlayerValue.uninitialized() - : this(duration: Duration.zero, isInitialized: false); + : this( + duration: DurationRange(Duration.zero, Duration.zero), + isInitialized: false); /// Returns an instance with the given [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) : this( - duration: Duration.zero, + duration: DurationRange(Duration.zero, Duration.zero), isInitialized: false, errorDescription: errorDescription); @@ -73,7 +75,7 @@ class VideoPlayerValue { /// The total duration of the video. /// /// The duration is [Duration.zero] if the video hasn't been initialized. - final Duration duration; + final DurationRange duration; /// The current playback position. final Duration position; @@ -145,7 +147,7 @@ class VideoPlayerValue { /// Returns a new instance that has the same values as this current instance, /// except for any overrides passed in as arguments to [copyWidth]. VideoPlayerValue copyWith({ - Duration? duration, + DurationRange? duration, Size? size, Duration? position, Caption? caption, @@ -226,7 +228,8 @@ class VideoPlayerController extends ValueNotifier { httpHeaders = const {}, drmConfigs = null, playerOptions = const {}, - super(VideoPlayerValue(duration: Duration.zero)); + super(VideoPlayerValue( + duration: DurationRange(Duration.zero, Duration.zero))); /// Constructs a [VideoPlayerController] playing a video from obtained from /// the network. @@ -247,7 +250,8 @@ class VideoPlayerController extends ValueNotifier { this.playerOptions, }) : dataSourceType = DataSourceType.network, package = null, - super(VideoPlayerValue(duration: Duration.zero)); + super(VideoPlayerValue( + duration: DurationRange(Duration.zero, Duration.zero))); /// Constructs a [VideoPlayerController] playing a video from a file. /// @@ -264,7 +268,8 @@ class VideoPlayerController extends ValueNotifier { httpHeaders = const {}, drmConfigs = null, playerOptions = const {}, - super(VideoPlayerValue(duration: Duration.zero)); + super(VideoPlayerValue( + duration: DurationRange(Duration.zero, Duration.zero))); /// Constructs a [VideoPlayerController] playing a video from a contentUri. /// @@ -283,7 +288,8 @@ class VideoPlayerController extends ValueNotifier { httpHeaders = const {}, drmConfigs = null, playerOptions = const {}, - super(VideoPlayerValue(duration: Duration.zero)); + super(VideoPlayerValue( + duration: DurationRange(Duration.zero, Duration.zero))); /// The URI to the video file. This will be in different formats depending on /// the [DataSourceType] of the original video. @@ -325,6 +331,7 @@ class VideoPlayerController extends ValueNotifier { ClosedCaptionFile? _closedCaptionFile; Timer? _timer; + Timer? _durationTimer; bool _isDisposed = false; Completer? _creatingCompleter; StreamSubscription? _eventSubscription; @@ -413,13 +420,16 @@ class VideoPlayerController extends ValueNotifier { _applyLooping(); _applyVolume(); _applyPlayPause(); + _durationTimer?.cancel(); + _durationTimer = _createDurationTimer(); break; case VideoEventType.completed: // In this case we need to stop _timer, set isPlaying=false, and // position=value.duration. Instead of setting the values directly, // we use pause() and seekTo() to ensure the platform stops playing // and seeks to the last frame of the video. - pause().then((void pauseResult) => seekTo(value.duration)); + pause().then((void pauseResult) => seekTo(value.duration.end)); + _durationTimer?.cancel(); break; case VideoEventType.bufferingUpdate: value = value.copyWith(buffered: event.buffered); @@ -434,7 +444,7 @@ class VideoPlayerController extends ValueNotifier { final Caption caption = Caption( number: 0, start: value.position, - end: value.position + (event.duration ?? Duration.zero), + end: value.position + (event.duration?.end ?? Duration.zero), text: event.text ?? '', ); value = value.copyWith(caption: caption); @@ -466,6 +476,7 @@ class VideoPlayerController extends ValueNotifier { final PlatformException e = obj as PlatformException; value = VideoPlayerValue.erroneous(e.message!); _timer?.cancel(); + _durationTimer?.cancel(); if (!initializingCompleter.isCompleted) { initializingCompleter.completeError(obj); } @@ -484,6 +495,7 @@ class VideoPlayerController extends ValueNotifier { if (!_isDisposed) { _isDisposed = true; _timer?.cancel(); + _durationTimer?.cancel(); await _eventSubscription?.cancel(); await _videoPlayerPlatform.dispose(_playerId); } @@ -501,7 +513,7 @@ class VideoPlayerController extends ValueNotifier { /// has been sent to the platform, not when playback itself is totally /// finished. Future play() async { - if (value.position == value.duration) { + if (value.position == value.duration.end) { await seekTo(Duration.zero); } value = value.copyWith(isPlaying: true); @@ -531,6 +543,30 @@ class VideoPlayerController extends ValueNotifier { await _applyPlayPause(); } + /// The duration in the current video. + Future get duration async { + if (_isDisposed) { + return null; + } + return _videoPlayerPlatform.getDuration(_playerId); + } + + Timer _createDurationTimer() { + return Timer.periodic( + const Duration(milliseconds: 1000), + (Timer timer) async { + if (_isDisposed) { + return; + } + final DurationRange? newDuration = await duration; + if (newDuration == null) { + return; + } + _updateDuration(newDuration); + }, + ); + } + Future _applyLooping() async { if (_isDisposedOrNotInitialized) { return; @@ -615,8 +651,8 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposedOrNotInitialized) { return; } - if (position > value.duration) { - position = value.duration; + if (position > value.duration.end) { + position = value.duration.end; } else if (position < Duration.zero) { position = Duration.zero; } @@ -745,6 +781,10 @@ class VideoPlayerController extends ValueNotifier { ); } + void _updateDuration(DurationRange duration) { + value = value.copyWith(duration: duration); + } + @override void removeListener(VoidCallback listener) { // Prevent VideoPlayer from causing an exception to be thrown when attempting to @@ -942,7 +982,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { final RenderBox box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); final double relative = tapPos.dx / box.size.width; - final Duration position = controller.value.duration * relative; + final Duration position = controller.value.duration.end * relative; controller.seekTo(position); } @@ -966,7 +1006,7 @@ class _VideoScrubberState extends State<_VideoScrubber> { }, onHorizontalDragEnd: (DragEndDetails details) { if (_controllerWasPlaying && - controller.value.position != controller.value.duration) { + controller.value.position != controller.value.duration.end) { controller.play(); } }, @@ -1059,7 +1099,7 @@ class _VideoProgressIndicatorState extends State { Widget build(BuildContext context) { Widget progressIndicator; if (controller.value.isInitialized) { - final int duration = controller.value.duration.inMilliseconds; + final int duration = controller.value.duration.end.inMilliseconds; final int position = controller.value.position.inMilliseconds; progressIndicator = Stack( diff --git a/packages/video_player_avplay/lib/video_player_platform_interface.dart b/packages/video_player_avplay/lib/video_player_platform_interface.dart index db56b9a3f..d1fb0e917 100644 --- a/packages/video_player_avplay/lib/video_player_platform_interface.dart +++ b/packages/video_player_avplay/lib/video_player_platform_interface.dart @@ -127,6 +127,11 @@ abstract class VideoPlayerPlatform extends PlatformInterface { throw UnimplementedError('getPosition() has not been implemented.'); } + /// Gets the video duration as [DurationRange]. + Future getDuration(int playerId) { + throw UnimplementedError('getDuration() has not been implemented.'); + } + /// Returns a widget displaying the video with a given playerId. Widget buildView(int playerId) { throw UnimplementedError('buildView() has not been implemented.'); @@ -271,7 +276,7 @@ class VideoEvent { /// Duration of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. - final Duration? duration; + final DurationRange? duration; /// Size of the video. /// diff --git a/packages/video_player_avplay/pigeons/messages.dart b/packages/video_player_avplay/pigeons/messages.dart index 4dbd9e026..5dc95fc38 100644 --- a/packages/video_player_avplay/pigeons/messages.dart +++ b/packages/video_player_avplay/pigeons/messages.dart @@ -82,6 +82,12 @@ class GeometryMessage { int height; } +class DurationMessage { + DurationMessage(this.playerId); + int playerId; + List? durationRange; +} + @HostApi() abstract class VideoPlayerAvplayApi { void initialize(); @@ -96,6 +102,7 @@ abstract class VideoPlayerAvplayApi { TrackMessage track(TrackTypeMessage msg); bool setTrackSelection(SelectedTracksMessage msg); PositionMessage position(PlayerMessage msg); + DurationMessage duration(PlayerMessage msg); @async void seekTo(PositionMessage msg); void pause(PlayerMessage msg); diff --git a/packages/video_player_avplay/pubspec.yaml b/packages/video_player_avplay/pubspec.yaml index 4b55c1697..f7ff96c32 100644 --- a/packages/video_player_avplay/pubspec.yaml +++ b/packages/video_player_avplay/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avplay description: Flutter plugin for displaying inline video on Tizen TV devices. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/video_player_avplay -version: 0.1.3 +version: 0.2.0 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/video_player_avplay/tizen/src/drm_manager.cc b/packages/video_player_avplay/tizen/src/drm_manager.cc index ddad2b170..1f3c83b8b 100644 --- a/packages/video_player_avplay/tizen/src/drm_manager.cc +++ b/packages/video_player_avplay/tizen/src/drm_manager.cc @@ -263,6 +263,7 @@ gboolean DrmManager::ProcessLicense(void *user_data) { data->session_id.c_str())), static_cast(response_data), reinterpret_cast(response_len)); + free(response_data); } else if (self->request_license_channel_) { // Get license via the Dart callback. self->RequestLicense(data->session_id, data->message); diff --git a/packages/video_player_avplay/tizen/src/media_player.cc b/packages/video_player_avplay/tizen/src/media_player.cc index 115a5a2bf..e55e55298 100644 --- a/packages/video_player_avplay/tizen/src/media_player.cc +++ b/packages/video_player_avplay/tizen/src/media_player.cc @@ -6,8 +6,20 @@ #include +#include + #include "log.h" +static std::vector split(const std::string &s, char delim) { + std::stringstream ss(s); + std::string item; + std::vector tokens; + while (getline(ss, item, delim)) { + tokens.push_back(item); + } + return tokens; +} + static std::string RotationToString(player_display_rotation_e rotation) { switch (rotation) { case PLAYER_DISPLAY_ROTATION_NONE: @@ -298,15 +310,54 @@ int64_t MediaPlayer::GetPosition() { return position; } -int64_t MediaPlayer::GetDuration() { - int duration = 0; - int ret = player_get_duration(player_, &duration); +bool MediaPlayer::IsLive() { + int is_live = 0; + int ret = media_player_proxy_->player_get_adaptive_streaming_info( + player_, &is_live, PLAYER_ADAPTIVE_INFO_IS_LIVE); if (ret != PLAYER_ERROR_NONE) { - LOG_ERROR("[MediaPlayer] player_get_duration failed: %s.", + LOG_ERROR("[MediaPlayer] player_get_adaptive_streaming_info failed: %s", get_error_message(ret)); + return false; + } + return is_live != 0; +} + +std::pair MediaPlayer::GetLiveDuration() { + std::string live_duration_str = ""; + char *live_duration_buff = static_cast(malloc(sizeof(char) * 64)); + memset(live_duration_buff, 0, sizeof(char) * 64); + int ret = media_player_proxy_->player_get_adaptive_streaming_info( + player_, (void *)&live_duration_buff, PLAYER_ADAPTIVE_INFO_LIVE_DURATION); + if (ret != PLAYER_ERROR_NONE) { + LOG_ERROR("[MediaPlayer] player_get_adaptive_streaming_info failed: %s", + get_error_message(ret)); + free(live_duration_buff); + return std::make_pair(0, 0); + } + if (*live_duration_buff) { + live_duration_str = std::string(live_duration_buff); + } + free(live_duration_buff); + if (live_duration_str.empty()) { + return std::make_pair(0, 0); + } + std::vector time_vec = split(live_duration_str, '|'); + return std::make_pair(std::stoll(time_vec[0]), std::stoll(time_vec[1])); +} + +std::pair MediaPlayer::GetDuration() { + if (IsLive()) { + return GetLiveDuration(); + } else { + int duration = 0; + int ret = player_get_duration(player_, &duration); + if (ret != PLAYER_ERROR_NONE) { + LOG_ERROR("[MediaPlayer] player_get_duration failed: %s.", + get_error_message(ret)); + } + LOG_INFO("[MediaPlayer] Video duration: %d.", duration); + return std::make_pair(0, duration); } - LOG_INFO("[MediaPlayer] Video duration: %d.", duration); - return duration; } void MediaPlayer::GetVideoSize(int32_t *width, int32_t *height) { diff --git a/packages/video_player_avplay/tizen/src/media_player.h b/packages/video_player_avplay/tizen/src/media_player.h index 01e0e91c6..cc43c9259 100644 --- a/packages/video_player_avplay/tizen/src/media_player.h +++ b/packages/video_player_avplay/tizen/src/media_player.h @@ -35,13 +35,15 @@ class MediaPlayer : public VideoPlayer { bool SetPlaybackSpeed(double speed) override; bool SeekTo(int64_t position, SeekCompletedCallback callback) override; int64_t GetPosition() override; - int64_t GetDuration() override; + std::pair GetDuration() override; void GetVideoSize(int32_t *width, int32_t *height) override; bool IsReady() override; flutter::EncodableList GetTrackInfo(std::string track_type) override; bool SetTrackSelection(int32_t track_id, std::string track_type) override; private: + bool IsLive(); + std::pair GetLiveDuration(); bool SetDisplay(); bool SetDrm(const std::string &uri, int drm_type, const std::string &license_server_url); diff --git a/packages/video_player_avplay/tizen/src/media_player_proxy.cc b/packages/video_player_avplay/tizen/src/media_player_proxy.cc index 06adafac1..751404309 100644 --- a/packages/video_player_avplay/tizen/src/media_player_proxy.cc +++ b/packages/video_player_avplay/tizen/src/media_player_proxy.cc @@ -21,6 +21,9 @@ typedef int (*FuncPlayerSetDrmInitCompleteCB)( typedef int (*FuncPlayerSetDrmInitDataCB)(player_h player, set_drm_init_data_cb callback, void* user_data); +typedef int (*FuncPlayerGetAdaptiveStreamingInfo)(player_h player, + void* adaptive_info, + int adaptive_type); typedef int (*FuncPlayerGetTrackCountV2)(player_h player, player_stream_type_e type, int* pcount); @@ -115,6 +118,24 @@ int MediaPlayerProxy::player_set_drm_init_data_cb(player_h player, return player_set_drm_init_data_cb(player, callback, user_data); } +int MediaPlayerProxy::player_get_adaptive_streaming_info(player_h player, + void* adaptive_info, + int adaptive_type) { + if (!media_player_handle_) { + LOG_ERROR("media_player_handle_ not valid"); + return PLAYER_ERROR_NOT_AVAILABLE; + } + FuncPlayerGetAdaptiveStreamingInfo player_get_adaptive_streaming_info = + reinterpret_cast( + dlsym(media_player_handle_, "player_get_adaptive_streaming_info")); + if (!player_get_adaptive_streaming_info) { + LOG_ERROR("Fail to find player_get_adaptive_streaming_info."); + return PLAYER_ERROR_NOT_AVAILABLE; + } + return player_get_adaptive_streaming_info(player, adaptive_info, + adaptive_type); +} + int MediaPlayerProxy::player_get_track_count_v2(player_h player, player_stream_type_e type, int* pcount) { diff --git a/packages/video_player_avplay/tizen/src/media_player_proxy.h b/packages/video_player_avplay/tizen/src/media_player_proxy.h index 6e9e94d90..1229a42b4 100644 --- a/packages/video_player_avplay/tizen/src/media_player_proxy.h +++ b/packages/video_player_avplay/tizen/src/media_player_proxy.h @@ -55,6 +55,44 @@ typedef enum { PLAYER_DRM_TYPE_MAX_COUNT, } player_drm_type_e; +typedef enum { + PLAYER_ADAPTIVE_INFO_BITRATE_INIT, + PLAYER_ADAPTIVE_INFO_BITRATE_INIT_NUM, + PLAYER_ADAPTIVE_INFO_DURATION, + PLAYER_ADAPTIVE_INFO_LIVE_DURATION, + PLAYER_ADAPTIVE_INFO_AVAILABLE_BITRATES, + PLAYER_ADAPTIVE_INFO_RATE_RETURNED, + PLAYER_ADAPTIVE_INFO_CURRENT_BITRATE, + PLAYER_ADAPTIVE_INFO_GET_BUFFER_SIZE, + PLAYER_ADAPTIVE_INFO_FIXED_BITRATE, + PLAYER_ADAPTIVE_INFO_ADAPTIVE_BITRATE, + PLAYER_ADAPTIVE_INFO_MAX_BYTES, + PLAYER_ADAPTIVE_INFO_DRM_TYPE, + PLAYER_ADAPTIVE_INFO_RATE_REQUESTED, + PLAYER_ADAPTIVE_INFO_AUDIO_TRACK_REQUESTED, + PLAYER_ADAPTIVE_INFO_FORMAT, + PLAYER_ADAPTIVE_INFO_BLOCK, + PLAYER_ADAPTIVE_INFO_MIN_PERCENT, + PLAYER_ADAPTIVE_INFO_MIN_LATENCY, + PLAYER_ADAPTIVE_INFO_MAX_LATENCY, + PLAYER_ADAPTIVE_INFO_IS_LIVE, + PLAYER_ADAPTIVE_INFO_EMIT_SIGNAL, + PLAYER_ADAPTIVE_INFO_LOG_LEVEL, + PLAYER_ADAPTIVE_INFO_CURRENT_BANDWIDTH, + PLAYER_ADAPTIVE_INFO_URL_CUSTOM, + PLAYER_ADAPTIVE_INFO_GET_AUDIO_INFO, + PLAYER_ADAPTIVE_INFO_GET_VIDEO_INFO, + PLAYER_ADAPTIVE_INFO_GET_TEXT_INFO, + PLAYER_ADAPTIVE_INFO_RESUME_TIME, + PLAYER_ADAPTIVE_INFO_AUDIO_INDEX, + PLAYER_ADAPTIVE_INFO_PROXY_SETTING, + PLAYER_ADAPTIVE_INFO_ATSC3_L1_SERVER_TIME, + PLAYER_ADAPTIVE_INFO_VIDEO_FRAMES_DROPPED, + PLAYER_ADAPTIVE_INFO_STREAMING_IS_BUFFERING, + PLAYER_ADAPTIVE_INFO_PRESELECTION_ID, + PLAYER_ADAPTIVE_INFO_URI_TYPE +} player_adaptive_Info_e; + typedef enum { CENC = 0, KEYIDS = 1, @@ -82,6 +120,8 @@ class MediaPlayerProxy { int player_set_drm_init_data_cb(player_h player, set_drm_init_data_cb callback, void* user_data); + int player_get_adaptive_streaming_info(player_h player, void* adaptive_info, + int adaptive_type); int player_get_track_count_v2(player_h player, player_stream_type_e type, int* pcount); int player_get_video_track_info_v2(player_h player, int index, diff --git a/packages/video_player_avplay/tizen/src/messages.cc b/packages/video_player_avplay/tizen/src/messages.cc index 79ec33fd0..1fbe61c17 100644 --- a/packages/video_player_avplay/tizen/src/messages.cc +++ b/packages/video_player_avplay/tizen/src/messages.cc @@ -487,6 +487,55 @@ GeometryMessage GeometryMessage::FromEncodableList(const EncodableList& list) { return decoded; } +// DurationMessage + +DurationMessage::DurationMessage(int64_t player_id) : player_id_(player_id) {} + +DurationMessage::DurationMessage(int64_t player_id, + const EncodableList* duration_range) + : player_id_(player_id), + duration_range_(duration_range + ? std::optional(*duration_range) + : std::nullopt) {} + +int64_t DurationMessage::player_id() const { return player_id_; } + +void DurationMessage::set_player_id(int64_t value_arg) { + player_id_ = value_arg; +} + +const EncodableList* DurationMessage::duration_range() const { + return duration_range_ ? &(*duration_range_) : nullptr; +} + +void DurationMessage::set_duration_range(const EncodableList* value_arg) { + duration_range_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void DurationMessage::set_duration_range(const EncodableList& value_arg) { + duration_range_ = value_arg; +} + +EncodableList DurationMessage::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(player_id_)); + list.push_back(duration_range_ ? EncodableValue(*duration_range_) + : EncodableValue()); + return list; +} + +DurationMessage DurationMessage::FromEncodableList(const EncodableList& list) { + DurationMessage decoded(list[0].LongValue()); + auto& encodable_duration_range = list[1]; + if (!encodable_duration_range.IsNull()) { + decoded.set_duration_range( + std::get(encodable_duration_range)); + } + return decoded; +} + VideoPlayerAvplayApiCodecSerializer::VideoPlayerAvplayApiCodecSerializer() {} EncodableValue VideoPlayerAvplayApiCodecSerializer::ReadValueOfType( @@ -496,33 +545,36 @@ EncodableValue VideoPlayerAvplayApiCodecSerializer::ReadValueOfType( return CustomEncodableValue(CreateMessage::FromEncodableList( std::get(ReadValue(stream)))); case 129: - return CustomEncodableValue(GeometryMessage::FromEncodableList( + return CustomEncodableValue(DurationMessage::FromEncodableList( std::get(ReadValue(stream)))); case 130: - return CustomEncodableValue(LoopingMessage::FromEncodableList( + return CustomEncodableValue(GeometryMessage::FromEncodableList( std::get(ReadValue(stream)))); case 131: - return CustomEncodableValue(MixWithOthersMessage::FromEncodableList( + return CustomEncodableValue(LoopingMessage::FromEncodableList( std::get(ReadValue(stream)))); case 132: - return CustomEncodableValue(PlaybackSpeedMessage::FromEncodableList( + return CustomEncodableValue(MixWithOthersMessage::FromEncodableList( std::get(ReadValue(stream)))); case 133: - return CustomEncodableValue(PlayerMessage::FromEncodableList( + return CustomEncodableValue(PlaybackSpeedMessage::FromEncodableList( std::get(ReadValue(stream)))); case 134: - return CustomEncodableValue(PositionMessage::FromEncodableList( + return CustomEncodableValue(PlayerMessage::FromEncodableList( std::get(ReadValue(stream)))); case 135: - return CustomEncodableValue(SelectedTracksMessage::FromEncodableList( + return CustomEncodableValue(PositionMessage::FromEncodableList( std::get(ReadValue(stream)))); case 136: - return CustomEncodableValue(TrackMessage::FromEncodableList( + return CustomEncodableValue(SelectedTracksMessage::FromEncodableList( std::get(ReadValue(stream)))); case 137: - return CustomEncodableValue(TrackTypeMessage::FromEncodableList( + return CustomEncodableValue(TrackMessage::FromEncodableList( std::get(ReadValue(stream)))); case 138: + return CustomEncodableValue(TrackTypeMessage::FromEncodableList( + std::get(ReadValue(stream)))); + case 139: return CustomEncodableValue(VolumeMessage::FromEncodableList( std::get(ReadValue(stream)))); default: @@ -542,8 +594,16 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( stream); return; } - if (custom_value->type() == typeid(GeometryMessage)) { + if (custom_value->type() == typeid(DurationMessage)) { stream->WriteByte(129); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(GeometryMessage)) { + stream->WriteByte(130); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -551,7 +611,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(LoopingMessage)) { - stream->WriteByte(130); + stream->WriteByte(131); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -559,7 +619,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(MixWithOthersMessage)) { - stream->WriteByte(131); + stream->WriteByte(132); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -567,7 +627,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PlaybackSpeedMessage)) { - stream->WriteByte(132); + stream->WriteByte(133); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -575,7 +635,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PlayerMessage)) { - stream->WriteByte(133); + stream->WriteByte(134); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -583,7 +643,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(PositionMessage)) { - stream->WriteByte(134); + stream->WriteByte(135); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -591,7 +651,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(SelectedTracksMessage)) { - stream->WriteByte(135); + stream->WriteByte(136); WriteValue( EncodableValue(std::any_cast(*custom_value) .ToEncodableList()), @@ -599,7 +659,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(TrackMessage)) { - stream->WriteByte(136); + stream->WriteByte(137); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -607,7 +667,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(TrackTypeMessage)) { - stream->WriteByte(137); + stream->WriteByte(138); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -615,7 +675,7 @@ void VideoPlayerAvplayApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(VolumeMessage)) { - stream->WriteByte(138); + stream->WriteByte(139); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -1030,6 +1090,40 @@ void VideoPlayerAvplayApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel->SetMessageHandler(nullptr); } } + { + auto channel = std::make_unique>( + binary_messenger, "dev.flutter.pigeon.VideoPlayerAvplayApi.duration", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_msg_arg = args.at(0); + if (encodable_msg_arg.IsNull()) { + reply(WrapError("msg_arg unexpectedly null.")); + return; + } + const auto& msg_arg = std::any_cast( + std::get(encodable_msg_arg)); + ErrorOr output = api->Duration(msg_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } { auto channel = std::make_unique>( binary_messenger, "dev.flutter.pigeon.VideoPlayerAvplayApi.seekTo", diff --git a/packages/video_player_avplay/tizen/src/messages.h b/packages/video_player_avplay/tizen/src/messages.h index d4e863492..03b606424 100644 --- a/packages/video_player_avplay/tizen/src/messages.h +++ b/packages/video_player_avplay/tizen/src/messages.h @@ -333,6 +333,32 @@ class GeometryMessage { int64_t height_; }; +// Generated class from Pigeon that represents data sent in messages. +class DurationMessage { + public: + // Constructs an object setting all non-nullable fields. + explicit DurationMessage(int64_t player_id); + + // Constructs an object setting all fields. + explicit DurationMessage(int64_t player_id, + const flutter::EncodableList* duration_range); + + int64_t player_id() const; + void set_player_id(int64_t value_arg); + + const flutter::EncodableList* duration_range() const; + void set_duration_range(const flutter::EncodableList* value_arg); + void set_duration_range(const flutter::EncodableList& value_arg); + + private: + static DurationMessage FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class VideoPlayerAvplayApi; + friend class VideoPlayerAvplayApiCodecSerializer; + int64_t player_id_; + std::optional duration_range_; +}; + class VideoPlayerAvplayApiCodecSerializer : public flutter::StandardCodecSerializer { public: @@ -370,6 +396,7 @@ class VideoPlayerAvplayApi { virtual ErrorOr Track(const TrackTypeMessage& msg) = 0; virtual ErrorOr SetTrackSelection(const SelectedTracksMessage& msg) = 0; virtual ErrorOr Position(const PlayerMessage& msg) = 0; + virtual ErrorOr Duration(const PlayerMessage& msg) = 0; virtual void SeekTo( const PositionMessage& msg, std::function reply)> result) = 0; diff --git a/packages/video_player_avplay/tizen/src/plus_player.cc b/packages/video_player_avplay/tizen/src/plus_player.cc index 834099393..bd9af8f72 100644 --- a/packages/video_player_avplay/tizen/src/plus_player.cc +++ b/packages/video_player_avplay/tizen/src/plus_player.cc @@ -305,39 +305,12 @@ bool PlusPlayer::SeekTo(int64_t position, SeekCompletedCallback callback) { } on_seek_completed_ = std::move(callback); - plusplayer::PlayerMemento memento; - if (!GetMemento(player_, &memento)) { - LOG_ERROR("[PlusPlayer] Player fail to get memento."); + if (!Seek(player_, position)) { + on_seek_completed_ = nullptr; + LOG_ERROR("[PlusPlayer] Player fail to seek."); + return false; } - if (memento.is_live) { - std::string str = GetStreamingProperty(player_, "GET_LIVE_DURATION"); - if (str.empty()) { - LOG_ERROR("[PlusPlayer] Player fail to get live duration."); - return false; - } - std::vector time_str = split(str, '|'); - int64_t start_time = std::stoll(time_str[0].c_str()); - int64_t end_time = std::stoll(time_str[1].c_str()); - - if (position < start_time || position > end_time) { - on_seek_completed_ = nullptr; - LOG_ERROR("[PlusPlayer] Position out of range."); - return false; - } - - if (!Seek(player_, position)) { - on_seek_completed_ = nullptr; - LOG_ERROR("[PlusPlayer] Player fail to seek."); - return false; - } - } else { - if (!Seek(player_, position)) { - on_seek_completed_ = nullptr; - LOG_ERROR("[PlusPlayer] Player fail to seek."); - return false; - } - } return true; } @@ -353,34 +326,40 @@ int64_t PlusPlayer::GetPosition() { return static_cast(position); } -int64_t PlusPlayer::GetDuration() { - int64_t duration = 0; - if (GetState(player_) >= plusplayer::State::kTrackSourceReady) { - plusplayer::PlayerMemento memento; - if (!GetMemento(player_, &memento)) { - LOG_ERROR("[PlusPlayer] Player fail to get memento."); - } +bool PlusPlayer::IsLive() { + plusplayer::PlayerMemento memento; + if (!GetMemento(player_, &memento)) { + LOG_ERROR("[PlusPlayer] Player fail to get memento."); + return false; + } - if (memento.is_live) { - std::string str = GetStreamingProperty(player_, "GET_LIVE_DURATION"); - if (str.empty()) { - LOG_ERROR("[PlusPlayer] Player fail to get live duration."); - return duration; - } - std::vector time_str = split(str, '|'); - int64_t start_time = std::stoll(time_str[0].c_str()); - int64_t end_time = std::stoll(time_str[1].c_str()); + return memento.is_live; +} - duration = end_time - start_time; - } else { - if (!::GetDuration(player_, &duration)) { - LOG_ERROR("[PlusPlayer] Player fail to get the duration."); - } - } +std::pair PlusPlayer::GetLiveDuration() { + std::string live_duration_str = + GetStreamingProperty(player_, "GET_LIVE_DURATION"); + if (live_duration_str.empty()) { + LOG_ERROR("[PlusPlayer] Player fail to get live duration."); + return std::make_pair(0, 0); } - LOG_INFO("[PlusPlayer] Video duration: %lld.", duration); - return duration; + std::vector time_vec = split(live_duration_str, '|'); + return std::make_pair(std::stoll(time_vec[0]), std::stoll(time_vec[1])); +} + +std::pair PlusPlayer::GetDuration() { + if (IsLive()) { + return GetLiveDuration(); + } else { + int64_t duration = 0; + if (!::GetDuration(player_, &duration)) { + LOG_ERROR("[PlusPlayer] Player fail to get the duration."); + return std::make_pair(0, 0); + } + LOG_INFO("[PlusPlayer] Video duration: %lld.", duration); + return std::make_pair(0, duration); + } } void PlusPlayer::GetVideoSize(int32_t *width, int32_t *height) { diff --git a/packages/video_player_avplay/tizen/src/plus_player.h b/packages/video_player_avplay/tizen/src/plus_player.h index 01dfc2dea..676e60cac 100644 --- a/packages/video_player_avplay/tizen/src/plus_player.h +++ b/packages/video_player_avplay/tizen/src/plus_player.h @@ -37,13 +37,15 @@ class PlusPlayer : public VideoPlayer { bool SetPlaybackSpeed(double speed) override; bool SeekTo(int64_t position, SeekCompletedCallback callback) override; int64_t GetPosition() override; - int64_t GetDuration() override; + std::pair GetDuration() override; void GetVideoSize(int32_t *width, int32_t *height) override; bool IsReady() override; flutter::EncodableList GetTrackInfo(std::string track_type) override; bool SetTrackSelection(int32_t track_id, std::string track_type) override; private: + bool IsLive(); + std::pair GetLiveDuration(); bool SetDisplay(); bool SetDrm(const std::string &uri, int drm_type, const std::string &license_server_url); diff --git a/packages/video_player_avplay/tizen/src/video_player.cc b/packages/video_player_avplay/tizen/src/video_player.cc index 38decd391..5c74b19cf 100644 --- a/packages/video_player_avplay/tizen/src/video_player.cc +++ b/packages/video_player_avplay/tizen/src/video_player.cc @@ -96,14 +96,18 @@ void VideoPlayer::PushEvent(flutter::EncodableValue encodable_value) { void VideoPlayer::SendInitialized() { if (!is_initialized_ && event_sink_) { int32_t width = 0, height = 0; - int64_t duration = GetDuration(); GetVideoSize(&width, &height); is_initialized_ = true; + auto duration = GetDuration(); + flutter::EncodableList duration_range{ + flutter::EncodableValue(duration.first), + flutter::EncodableValue(duration.second)}; + flutter::EncodableMap result = { {flutter::EncodableValue("event"), flutter::EncodableValue("initialized")}, {flutter::EncodableValue("duration"), - flutter::EncodableValue(duration)}, + flutter::EncodableValue(duration_range)}, {flutter::EncodableValue("width"), flutter::EncodableValue(width)}, {flutter::EncodableValue("height"), flutter::EncodableValue(height)}, }; diff --git a/packages/video_player_avplay/tizen/src/video_player.h b/packages/video_player_avplay/tizen/src/video_player.h index 53a522288..829190d53 100644 --- a/packages/video_player_avplay/tizen/src/video_player.h +++ b/packages/video_player_avplay/tizen/src/video_player.h @@ -45,7 +45,7 @@ class VideoPlayer { virtual bool SetPlaybackSpeed(double speed) = 0; virtual bool SeekTo(int64_t position, SeekCompletedCallback callback) = 0; virtual int64_t GetPosition() = 0; - virtual int64_t GetDuration() = 0; + virtual std::pair GetDuration() = 0; virtual bool IsReady() = 0; virtual flutter::EncodableList GetTrackInfo(std::string track_type) = 0; virtual bool SetTrackSelection(int32_t track_id, std::string track_type) = 0; diff --git a/packages/video_player_avplay/tizen/src/video_player_tizen_plugin.cc b/packages/video_player_avplay/tizen/src/video_player_tizen_plugin.cc index 10cb39eb4..1b2dfa885 100644 --- a/packages/video_player_avplay/tizen/src/video_player_tizen_plugin.cc +++ b/packages/video_player_avplay/tizen/src/video_player_tizen_plugin.cc @@ -46,6 +46,7 @@ class VideoPlayerTizenPlugin : public flutter::Plugin, ErrorOr SetDeactivate(const PlayerMessage &msg) override; ErrorOr SetActivate(const PlayerMessage &msg) override; ErrorOr Position(const PlayerMessage &msg) override; + ErrorOr Duration(const PlayerMessage &msg) override; void SeekTo( const PositionMessage &msg, std::function reply)> result) override; @@ -308,6 +309,21 @@ ErrorOr VideoPlayerTizenPlugin::Position( return result; } +ErrorOr VideoPlayerTizenPlugin::Duration( + const PlayerMessage &msg) { + VideoPlayer *player = FindPlayerById(msg.player_id()); + if (!player) { + return FlutterError("Invalid argument", "Player not found"); + } + DurationMessage result(msg.player_id()); + auto duration_pair = player->GetDuration(); + flutter::EncodableList duration_range{ + flutter::EncodableValue(duration_pair.first), + flutter::EncodableValue(duration_pair.second)}; + result.set_duration_range(duration_range); + return result; +} + void VideoPlayerTizenPlugin::SeekTo( const PositionMessage &msg, std::function reply)> result) {