Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/video_player_videohole/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.10
Comment thread
CodeToolPerson marked this conversation as resolved.

* Fix crash/seek failures on progressive HTTP MPEG-TS live streams.
* Avoid treating all `.ts` URLs as live so finite MPEG-TS VOD stays seekable.

## 0.5.9

* Adds compatibility with `http` 1.0 in example.
Expand Down
43 changes: 34 additions & 9 deletions packages/video_player_videohole/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -469,19 +469,23 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
if (VideoEventType.restored == event.eventType &&
_onRestoreDataSource != null) {
play();
} else {
} else if (value.isPlaying) {
// Avoid pause before play; live/init can report Pause failed.
_applyPlayPause();
}
_durationTimer?.cancel();
_durationTimer = _createDurationTimer();
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.end));
value = value.copyWith(isCompleted: true);
_durationTimer?.cancel();
// Live / placeholder duration: skip pause+seek (plain .ts fails).
if (_looksLikeLiveOrOpenEnded) {
value = value.copyWith(isPlaying: false, isCompleted: true);
_timer?.cancel();
_durationTimer?.cancel();
} else {
pause().then((void pauseResult) => seekTo(value.duration.end));
value = value.copyWith(isCompleted: true);
_durationTimer?.cancel();
}
case VideoEventType.bufferingUpdate:
value = value.copyWith(buffered: event.buffered);
case VideoEventType.bufferingStart:
Expand Down Expand Up @@ -573,13 +577,31 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
/// has been sent to the platform, not when playback itself is totally
/// finished.
Future<void> play() async {
if (value.position == value.duration.end) {
// Do not seekTo(0) when duration is ~0 or at end; live MPEG-TS is
// often not seekable.
final Duration end = value.duration.end;
if (!_looksLikeLiveOrOpenEnded &&
end > Duration.zero &&
value.position >= end) {
await seekTo(Duration.zero);
}
value = value.copyWith(isPlaying: true);
await _applyPlayPause();
}

/// Live / open-ended heuristic: tiny/placeholder duration, or `/live/` in URI.
///
/// Do not treat every `.ts` URL as live — finite MPEG-TS VOD must remain
/// seekable. Progressive live `.ts` typically reports a ~0/1ms duration from
/// the platform and is covered by the duration check.
bool get _looksLikeLiveOrOpenEnded {
final Duration end = value.duration.end;
if (end <= const Duration(milliseconds: 1)) {
return true;
}
return dataSource.toLowerCase().contains('/live/');
}

/// Sets the video activated. Use it if create two native players.
Future<bool> activate() async {
return _applyActivate();
Expand Down Expand Up @@ -721,6 +743,9 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
if (_isDisposedOrNotInitialized) {
return;
}
if (_looksLikeLiveOrOpenEnded) {
return;
}
if (position > value.duration.end) {
position = value.duration.end;
} else if (position < Duration.zero) {
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player_videohole/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: video_player_videohole
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_videohole
version: 0.5.9
version: 0.5.10

environment:
sdk: ">=3.1.0 <4.0.0"
Expand Down
83 changes: 65 additions & 18 deletions packages/video_player_videohole/tizen/src/media_player.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <dlfcn.h>

#include <algorithm>
#include <cctype>
#include <sstream>

#include "log.h"
Expand Down Expand Up @@ -231,13 +233,13 @@ bool MediaPlayer::Pause() {
if (ret != PLAYER_ERROR_NONE) {
LOG_ERROR("[MediaPlayer] Unable to get player state.");
}
if (state == PLAYER_STATE_NONE || state == PLAYER_STATE_IDLE) {
LOG_ERROR("[MediaPlayer] Player not ready.");
return false;
}
if (state != PLAYER_STATE_PLAYING) {
LOG_INFO("[MediaPlayer] Player not playing.");
return false;
// Treat pause as success when not PLAYING (e.g. live init) so Dart
// does not see a PlatformException.
if (state == PLAYER_STATE_NONE || state == PLAYER_STATE_IDLE ||
state != PLAYER_STATE_PLAYING) {
LOG_INFO("[MediaPlayer] Player not playing; treat pause as no-op success.");
SendIsPlayingState(false);
return true;
}
ret = player_pause(player_);
if (ret != PLAYER_ERROR_NONE) {
Expand Down Expand Up @@ -287,6 +289,24 @@ bool MediaPlayer::SetPlaybackSpeed(double speed) {
bool MediaPlayer::SeekTo(int64_t position, SeekCompletedCallback callback) {
LOG_INFO("[MediaPlayer] position: %lld.", position);

// Live / open-ended streams are typically not seekable.
if (IsAdaptiveLive() || IsProgressiveLiveUri()) {
LOG_INFO("[MediaPlayer] Skip seek on live stream.");
if (callback) {
callback();
}
return true;
}
int duration_ms = 0;
int duration_ret = player_get_duration(player_, &duration_ms);
if (duration_ret == PLAYER_ERROR_NONE && duration_ms <= 0) {
LOG_INFO("[MediaPlayer] Skip seek on open-ended stream (duration=0).");
if (callback) {
callback();
}
return true;
}

on_seek_completed_ = std::move(callback);
int ret =
player_set_play_position(player_, position, true, OnSeekCompleted, this);
Expand All @@ -311,18 +331,30 @@ int64_t MediaPlayer::GetPosition() {
}

std::pair<int64_t, int64_t> 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));
// Only adaptive live may use GetLiveDuration. Calling that API on
// progressive MPEG-TS can SIGSEGV.
if (IsAdaptiveLive()) {
std::pair<int64_t, int64_t> live = GetLiveDuration();
if (live.second <= 0) {
LOG_INFO("[MediaPlayer] Live duration unavailable; use placeholder 1ms.");
return std::make_pair(0, 1);
}
LOG_INFO("[MediaPlayer] Video duration: %d.", duration);
return std::make_pair(0, duration);
return live;
}

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);
// Open-ended / progressive live often reports 0; use 1ms so Dart does not
// treat the stream as ended and call seekTo(0).
if (duration <= 0) {
duration = 1;
}
return std::make_pair(0, duration);
}

void MediaPlayer::GetVideoSize(int32_t *width, int32_t *height) {
Expand Down Expand Up @@ -391,7 +423,18 @@ bool MediaPlayer::SetDisplay() {
return true;
}

bool MediaPlayer::IsLive() {
bool MediaPlayer::IsProgressiveLiveUri() const {
// Path hint only — do not treat every ".ts" URL as live (VOD MPEG-TS
// must remain seekable). Progressive live without "/live/" is handled via
// duration==0 in GetDuration/SeekTo.
std::string lower = url_;
std::transform(
lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return lower.find("/live/") != std::string::npos;
}

bool MediaPlayer::IsAdaptiveLive() {
int is_live = 0;
int ret = media_player_proxy_->player_get_adaptive_streaming_info(
player_, &is_live, PLAYER_ADAPTIVE_INFO_IS_LIVE);
Expand All @@ -403,6 +446,10 @@ bool MediaPlayer::IsLive() {
return is_live != 0;
}

bool MediaPlayer::IsLive() {
return IsProgressiveLiveUri() || IsAdaptiveLive();
}

static std::vector<std::string> split(const std::string &s, char delim) {
std::stringstream ss(s);
std::string item;
Expand Down
2 changes: 2 additions & 0 deletions packages/video_player_videohole/tizen/src/media_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class MediaPlayer : public VideoPlayer {
private:
std::pair<int64_t, int64_t> GetLiveDuration();
bool IsLive();
bool IsAdaptiveLive();
bool IsProgressiveLiveUri() const;
bool SetDisplay();
bool SetDrm(const std::string &uri, int drm_type,
const std::string &license_server_url);
Expand Down
Loading