Skip to content
Merged
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
1 change: 1 addition & 0 deletions assets/translations/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "نصٌّ تجريبيٌّ لمعاينة شكل الترجمة",
"server": "الخادم",
"change_server": "تغيير الخادم",
"cannot_seek_server": "لا يمكن التقديم على هذا الخادم. شاهد من البداية أو اختر خادمًا آخر.",
"stream_not_found": "يُخفي هذا المصدر بثّه خلف مشغّل محمي. جرّب مصدراً آخر، أو افتحه في متصفّحك.",
"subtitle_translation": "ترجمة النصوص",
"auto_translate": "الترجمة التلقائية",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/ckb.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "ڕێوییە قاوەییە خێراکە بەسەر سەگە تەمەڵەکەدا باز دەدات",
"server": "سێرڤەر",
"change_server": "گۆڕینی سێرڤەر",
"cannot_seek_server": "لەم سێرڤەرەدا ناتوانرێت بازبدرێت بۆ پێشەوە. لە سەرەتاوە سەیری بکە یان سێرڤەرێکی تر هەڵبژێرە.",
"stream_not_found": "ئەم سەرچاوەیە پەخشەکەی لە پشت لێدەرێکی پارێزراو شاردۆتەوە. سەرچاوەیەکی تر تاقیبکەرەوە، یان لە وێبگەڕەکەتدا بیکەرەوە.",
"subtitle_translation": "وەرگێڕانی ژێرنووس",
"auto_translate": "وەرگێڕانی خۆکار",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern",
"server": "Server",
"change_server": "Server wechseln",
"cannot_seek_server": "Auf diesem Server kann man nicht vorspulen. Schau von Anfang an oder wähle einen anderen Server.",
"stream_not_found": "Diese Quelle versteckt ihren Stream hinter einem geschützten Player. Probiere eine andere Quelle oder öffne sie im Browser.",
"subtitle_translation": "Untertitelübersetzung",
"auto_translate": "Automatisch übersetzen",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "The quick brown fox jumps over the lazy dog",
"server": "Server",
"change_server": "Change server",
"cannot_seek_server": "This server can't skip ahead. Play from the start, or pick another server.",
"stream_not_found": "This source hides its stream behind a protected player. Try another source, or open it in your browser.",
"subtitle_translation": "Subtitle translation",
"auto_translate": "Auto-translate",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "El veloz murciélago hindú comía feliz cardillo y kiwi",
"server": "Servidor",
"change_server": "Cambiar servidor",
"cannot_seek_server": "Este servidor no permite adelantar. Míralo desde el principio o elige otro servidor.",
"stream_not_found": "Esta fuente esconde su flujo detrás de un reproductor protegido. Prueba otra fuente o ábrela en el navegador.",
"subtitle_translation": "Traducción de subtítulos",
"auto_translate": "Traducir automáticamente",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Portez ce vieux whisky au juge blond qui fume",
"server": "Serveur",
"change_server": "Changer de serveur",
"cannot_seek_server": "Ce serveur ne permet pas d'avancer. Regardez depuis le début ou choisissez un autre serveur.",
"stream_not_found": "Cette source cache son flux derrière un lecteur protégé. Essaie une autre source, ou ouvre-la dans ton navigateur.",
"subtitle_translation": "Traduction des sous-titres",
"auto_translate": "Traduction automatique",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "The quick brown fox jumps over the lazy dog",
"server": "सर्वर",
"change_server": "सर्वर बदलें",
"cannot_seek_server": "इस सर्वर पर आगे नहीं बढ़ा सकते। शुरू से देखें या कोई दूसरा सर्वर चुनें।",
"stream_not_found": "यह सोर्स अपनी स्ट्रीम को एक सुरक्षित प्लेयर के पीछे छिपाता है। कोई दूसरा सोर्स आज़माएँ या इसे अपने ब्राउज़र में खोलें।",
"subtitle_translation": "सबटाइटल अनुवाद",
"auto_translate": "ऑटो-अनुवाद",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Muharjo seorang xenofobia universal yang takut pada kelinci",
"server": "Server",
"change_server": "Ganti server",
"cannot_seek_server": "Server ini tidak bisa dipercepat. Tonton dari awal atau pilih server lain.",
"stream_not_found": "Sumber ini menyembunyikan alirannya di balik pemutar terlindungi. Coba sumber lain, atau buka di peramban.",
"subtitle_translation": "Terjemahan subtitle",
"auto_translate": "Terjemahkan otomatis",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/ku.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "Rovîyê qehweyî yê bilez di ser kûçikê tembel re diqevze",
"server": "Server",
"change_server": "Serverê biguherîne",
"cannot_seek_server": "Li ser vê serverê nikare were pêşve birin. Ji destpêkê temaşe bike an serverek din hilbijêre.",
"stream_not_found": "Ev çavkanî weşana xwe li pişt lêdarekî parastî vedişêre. Çavkaniyeke din biceribîne, an wê di gerokê de veke.",
"subtitle_translation": "Wergera binnivîsê",
"auto_translate": "Wergera otomatîk",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Pa's wijze lynx bezag vroom het fikse aquaduct",
"server": "Server",
"change_server": "Server wisselen",
"cannot_seek_server": "Op deze server kun je niet vooruitspoelen. Kijk vanaf het begin of kies een andere server.",
"stream_not_found": "Deze bron verstopt zijn stream achter een beveiligde speler. Probeer een andere bron of open hem in je browser.",
"subtitle_translation": "Ondertitelvertaling",
"auto_translate": "Automatisch vertalen",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Um pequeno jabuti xereta viu dez cegonhas felizes",
"server": "Servidor",
"change_server": "Trocar de servidor",
"cannot_seek_server": "Este servidor não permite avançar. Assista desde o início ou escolha outro servidor.",
"stream_not_found": "Esta fonte esconde o stream atrás de um player protegido. Tente outra fonte ou abra no navegador.",
"subtitle_translation": "Tradução de legendas",
"auto_translate": "Traduzir automaticamente",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Съешь ещё этих мягких французских булок",
"server": "Сервер",
"change_server": "Сменить сервер",
"cannot_seek_server": "На этом сервере нельзя перематывать. Смотрите с начала или выберите другой сервер.",
"stream_not_found": "Этот источник прячет поток за защищённым плеером. Выберите другой источник или откройте в браузере.",
"subtitle_translation": "Перевод субтитров",
"auto_translate": "Автоперевод",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Pijamalı hasta yağız şoföre çabucak güvendi",
"server": "Sunucu",
"change_server": "Sunucuyu değiştir",
"cannot_seek_server": "Bu sunucuda ileri sarılamıyor. Baştan izleyin veya başka bir sunucu seçin.",
"stream_not_found": "Bu kaynak akışını korumalı bir oynatıcının arkasına saklıyor. Başka bir kaynak dene ya da tarayıcında aç.",
"subtitle_translation": "Altyazı çevirisi",
"auto_translate": "Otomatik çevir",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/uz.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
"subtitle_preview_text": "Tez qo’ng’ir tulki yalqov itning ustidan sakraydi",
"server": "Server",
"change_server": "Serverni almashtirish",
"cannot_seek_server": "Bu serverda oldinga o'tkazib bo'lmaydi. Boshidan ko'ring yoki boshqa server tanlang.",
"stream_not_found": "Bu manba oqimini himoyalangan pleyer ortida yashiradi. Boshqa manbani tanlang yoki brauzerda oching.",
"subtitle_translation": "Subtitr tarjimasi",
"auto_translate": "Avtomatik tarjima",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "Con cáo nâu nhanh nhẹn nhảy qua chú chó lười",
"server": "Máy chủ",
"change_server": "Đổi máy chủ",
"cannot_seek_server": "Máy chủ này không tua được. Hãy xem từ đầu hoặc chọn máy chủ khác.",
"stream_not_found": "Nguồn này giấu luồng phát sau một trình phát được bảo vệ. Hãy thử nguồn khác hoặc mở trong trình duyệt.",
"subtitle_translation": "Dịch phụ đề",
"auto_translate": "Tự động dịch",
Expand Down
1 change: 1 addition & 0 deletions assets/translations/yue.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
"subtitle_preview_text": "敏捷嘅棕色狐狸跳過隻懶狗",
"server": "伺服器",
"change_server": "更換伺服器",
"cannot_seek_server": "呢個伺服器唔可以快轉。請由頭睇,或者揀另一個伺服器。",
"stream_not_found": "呢個來源將串流收埋喺受保護嘅播放器後面。試下另一個來源,或者喺瀏覽器度開。",
"subtitle_translation": "字幕翻譯",
"auto_translate": "自動翻譯",
Expand Down
18 changes: 18 additions & 0 deletions lib/core/player/media_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ abstract class PlayerController extends ValueNotifier<vp.VideoPlayerValue> {
/// subtitle style, the same way it draws a downloaded subtitle file.
ValueListenable<String> get embeddedSubtitleText => _noEmbeddedText;

/// Turns true when the server refused a jump: the stream plays, but only
/// from where it is. mpv only; it never fires on the platform player.
ValueListenable<bool> get seekRefused => _neverRefused;

/// Whether this backend can adjust the picture while playing.
///
/// libmpv only. The platform player exposes no runtime video equalizer at
Expand Down Expand Up @@ -438,6 +442,7 @@ class PlayerAudioTrack {
}

final ValueNotifier<String> _noEmbeddedText = ValueNotifier<String>('');
final ValueNotifier<bool> _neverRefused = ValueNotifier<bool>(false);

/// One subtitle track inside the stream.
class PlayerSubtitleTrack extends PlayerAudioTrack {
Expand Down Expand Up @@ -696,6 +701,7 @@ class _MediaKitController extends PlayerController {
List<PlayerSubtitleTrack> _subtitleTracks = const <PlayerSubtitleTrack>[];
String? _activeSubtitleTrackId;
final ValueNotifier<String> _embeddedText = ValueNotifier<String>('');
final ValueNotifier<bool> _seekRefused = ValueNotifier<bool>(false);
String? _activeVideoTrackId;
final List<StreamSubscription<dynamic>> _subs =
<StreamSubscription<dynamic>>[];
Expand Down Expand Up @@ -1127,6 +1133,13 @@ class _MediaKitController extends PlayerController {
)
..add(
_player.stream.error.listen((e) {
// Reported as an error, it reopened the url at 0:00 — "skip 30 s
// starts it from the start" on UHD Movies' Google links, which
// ignore byte ranges. The film itself plays on.
if (isSeekRefusal(e)) {
_seekRefused.value = true;
return;
}
// A live stream has no length and no cache to ride out a stall
// on: once it plays, a dropped connection is reported at once so
// the page reconnects instead of showing a frozen picture.
Expand Down Expand Up @@ -1494,6 +1507,10 @@ class _MediaKitController extends PlayerController {
ValueListenable<String> get embeddedSubtitleText =>
_fallback != null ? _noEmbeddedText : _embeddedText;

@override
ValueListenable<bool> get seekRefused =>
_fallback != null ? _neverRefused : _seekRefused;

@override
Future<void> setSubtitleTrack(String id) async {
if (_fallback != null) return;
Expand Down Expand Up @@ -1556,6 +1573,7 @@ class _MediaKitController extends PlayerController {

Future<void> _disposeOnce() async {
if (!_closed.isCompleted) _closed.complete();
_seekRefused.dispose();
await _teardownMpv();
await _backendSwitches.close();
await _fallback?.dispose();
Expand Down
9 changes: 9 additions & 0 deletions lib/core/player/playback_readiness.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,12 @@ bool isDecodeGlitch(String error) {
final l = error.toLowerCase();
return l.startsWith('error decoding') || l.contains('error while decoding');
}

/// mpv's notice that the server ignores byte ranges: "Cannot seek in this
/// stream." and its "--force-seekable=yes" hint. The file keeps playing; only a
/// jump is refused, so it is not a playback error.
bool isSeekRefusal(String error) {
final l = error.toLowerCase();
return l.contains('cannot seek in this stream') ||
l.contains('force-seekable');
}
1 change: 1 addition & 0 deletions lib/features/detail/presentation/pages/player_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,7 @@ class _PlayerPageState extends State<PlayerPage>
final c = _controller;
if (c != null) {
c.removeListener(_onMajorChange);
c.seekRefused.removeListener(_onSeekRefused);
try {
c.pause();
} catch (_) {}
Expand Down
2 changes: 2 additions & 0 deletions lib/features/detail/presentation/pages/player_page.media.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,7 @@ extension _PlayerMedia on _PlayerPageState {
// Preview decoding starts only when the viewer scrubs. Eager warming
// opens a second video decoder while a high-resolution stream starts.
controller.addListener(_onMajorChange);
controller.seekRefused.addListener(_onSeekRefused);
await controller.setLooping(false);
if (!mounted || generation != _mediaGeneration) return;
if (party != null) {
Expand Down Expand Up @@ -2687,6 +2688,7 @@ extension _PlayerMedia on _PlayerPageState {
unawaited(FramePreviewService.close());
if (c != null) {
c.removeListener(_onMajorChange);
c.seekRefused.removeListener(_onSeekRefused);
try {
await c.pause();
} catch (_) {}
Expand Down
16 changes: 16 additions & 0 deletions lib/features/detail/presentation/pages/player_page.toast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ extension _PlayerToasts on _PlayerPageState {
});
}

/// The server ignores byte ranges: the film plays, but a skip cannot land.
/// Said once per stream, with the way out when this title has other servers.
void _onSeekRefused() {
final c = _controller;
if (c == null || !c.seekRefused.value) return;
_plog('stream is not seekable on this server', level: LogLevel.warn);
final servers = _affordances.hasServers;
_toast(
'player.cannot_seek_server'.tr(),
icon: Icons.fast_forward_rounded,
duration: const Duration(seconds: 6),
action: servers ? 'player.change_server'.tr() : null,
onAction: servers ? _openServerSheet : null,
);
}

void _dismissToast() {
_toastTimer?.cancel();
_toastNote.value = null;
Expand Down
11 changes: 11 additions & 0 deletions test/core/player/playback_readiness_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,15 @@ void main() {
expect(isDecodeGlitch('Could not open codec.'), isFalse);
expect(isDecodeGlitch('tcp: ffurl_read returned 0xffffff92'), isFalse);
});

test('a refused seek is a notice, not a playback failure', () {
// UHD Movies' Google links ignore byte ranges (support ticket, 2026-11-09).
expect(isSeekRefusal('Cannot seek in this stream.'), isTrue);
expect(
isSeekRefusal("You can force it with '--force-seekable=yes'."),
isTrue,
);
expect(isSeekRefusal('tcp: ffurl_read returned 0xffffff92'), isFalse);
expect(isSeekRefusal('Failed to open https://example.com/a.mkv.'), isFalse);
});
}
Loading