feat: add PiP error notifications

This commit is contained in:
edde746
2026-01-21 23:58:16 +01:00
parent 19049365cc
commit fb9105f84e
21 changed files with 343 additions and 40 deletions
@@ -2,7 +2,9 @@ package com.edde746.plezy
import android.os.Build
import android.os.Bundle
import android.app.AppOpsManager
import android.app.PictureInPictureParams
import android.content.Context
import android.content.res.Configuration
import android.util.Rational
import io.flutter.embedding.android.FlutterActivity
@@ -34,13 +36,41 @@ class MainActivity : FlutterActivity() {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
}
"enter" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
result.success(mapOf("success" to false, "errorCode" to "android_version"))
return@setMethodCallHandler
}
// Check if PiP permission is granted via AppOpsManager
val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val pipAllowed = appOpsManager.checkOpNoThrow(
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
applicationInfo.uid,
packageName
) == AppOpsManager.MODE_ALLOWED
if (!pipAllowed) {
result.success(mapOf("success" to false, "errorCode" to "permission_disabled"))
return@setMethodCallHandler
}
try {
val width = call.argument<Int>("width") ?: 16
val height = call.argument<Int>("height") ?: 9
val params = PictureInPictureParams.Builder().setAspectRatio(Rational(width, height)).build()
enterPictureInPictureMode(params)
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(width, height))
.build()
val success = enterPictureInPictureMode(params)
if (success) {
result.success(mapOf("success" to true))
} else {
result.success(mapOf("success" to false, "errorCode" to "failed"))
}
} catch (e: IllegalStateException) {
result.success(mapOf("success" to false, "errorCode" to "not_supported"))
} catch (e: Exception) {
result.success(mapOf("success" to false, "errorCode" to "unknown", "errorMessage" to (e.message ?: "Unknown error")))
}
result.success(null)
} else -> result.notImplemented()
}
}
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "Dreh­sperre",
"timelineSlider": "Video-Zeitleiste",
"volumeSlider": "Lautstärkepegel",
"backButton": "Zurück"
"backButton": "Zurück",
"pipFailed": "Bild-in-Bild konnte nicht gestartet werden",
"pipErrors": {
"androidVersion": "Erfordert Android 8.0 oder neuer",
"permissionDisabled": "Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild",
"notSupported": "Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht",
"failed": "Bild-in-Bild konnte nicht gestartet werden",
"unknown": "Ein Fehler ist aufgetreten: ${error}"
}
},
"userStatus": {
"admin": "Eigentümer",
+9 -1
View File
@@ -289,7 +289,15 @@
"rotationLockButton": "Rotation lock",
"timelineSlider": "Video timeline",
"volumeSlider": "Volume level",
"backButton": "Back"
"backButton": "Back",
"pipFailed": "Picture-in-picture failed to start",
"pipErrors": {
"androidVersion": "Requires Android 8.0 or newer",
"permissionDisabled": "Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture",
"notSupported": "Device doesn't support picture-in-picture mode",
"failed": "Picture-in-picture failed to start",
"unknown": "An error occurred: ${error}"
}
},
"userStatus": {
"admin": "Admin",
+9 -1
View File
@@ -289,7 +289,15 @@
"rotationLockButton": "Verrouillage de rotation",
"timelineSlider": "Timeline vidéo",
"volumeSlider": "Niveau sonore",
"backButton": "Retour"
"backButton": "Retour",
"pipFailed": "Échec du démarrage du mode image dans l'image",
"pipErrors": {
"androidVersion": "Nécessite Android 8.0 ou plus récent",
"permissionDisabled": "L'autorisation Image dans l'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l'image",
"notSupported": "Cet appareil ne prend pas en charge le mode image dans l'image",
"failed": "Échec du démarrage du mode image dans l'image",
"unknown": "Une erreur s'est produite : ${error}"
}
},
"userStatus": {
"admin": "Admin",
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "Blocco rotazione",
"timelineSlider": "Timeline video",
"volumeSlider": "Livello volume",
"backButton": "Indietro"
"backButton": "Indietro",
"pipFailed": "Impossibile avviare la modalità Picture-in-Picture",
"pipErrors": {
"androidVersion": "Richiede Android 8.0 o versioni successive",
"permissionDisabled": "L'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture",
"notSupported": "Questo dispositivo non supporta la modalità Picture-in-Picture",
"failed": "Impossibile avviare la modalità Picture-in-Picture",
"unknown": "Si è verificato un errore: ${error}"
}
},
"userStatus": {
"admin": "Admin",
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "회전 잠금",
"timelineSlider": "타임라인",
"volumeSlider": "볼륨 조절",
"backButton": "뒤로 가기"
"backButton": "뒤로 가기",
"pipFailed": "화면 속 화면 모드를 시작할 수 없습니다",
"pipErrors": {
"androidVersion": "Android 8.0 이상이 필요합니다",
"permissionDisabled": "화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요",
"notSupported": "이 기기는 화면 속 화면 모드를 지원하지 않습니다",
"failed": "화면 속 화면 모드를 시작할 수 없습니다",
"unknown": "오류가 발생했습니다: ${error}"
}
},
"userStatus": {
"admin": "관리자",
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "Rotatievergrendeling",
"timelineSlider": "Videotijdlijn",
"volumeSlider": "Volumeniveau",
"backButton": "Terug"
"backButton": "Terug",
"pipFailed": "Beeld-in-beeld kon niet worden gestart",
"pipErrors": {
"androidVersion": "Vereist Android 8.0 of nieuwer",
"permissionDisabled": "Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld",
"notSupported": "Dit apparaat ondersteunt geen beeld-in-beeld modus",
"failed": "Beeld-in-beeld kon niet worden gestart",
"unknown": "Er is een fout opgetreden: ${error}"
}
},
"userStatus": {
"admin": "Beheerder",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 8
/// Strings: 4324 (540 per locale)
/// Strings: 4372 (546 per locale)
///
/// Built on 2026-01-20 at 15:18 UTC
/// Built on 2026-01-21 at 22:57 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsDe implements TranslationsVideoControlsEn {
@override String get timelineSlider => 'Video-Zeitleiste';
@override String get volumeSlider => 'Lautstärkepegel';
@override String get backButton => 'Zurück';
@override String get pipFailed => 'Bild-in-Bild konnte nicht gestartet werden';
@override late final _TranslationsVideoControlsPipErrorsDe pipErrors = _TranslationsVideoControlsPipErrorsDe._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsDe implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => 'Zum vorherigen Untertitel springen';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsDe implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsDe._(this._root);
final TranslationsDe _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Erfordert Android 8.0 oder neuer';
@override String get permissionDisabled => 'Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild';
@override String get notSupported => 'Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht';
@override String get failed => 'Bild-in-Bild konnte nicht gestartet werden';
@override String unknown({required Object error}) => 'Ein Fehler ist aufgetreten: ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsDe implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsDe._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsDe {
'videoControls.timelineSlider' => 'Video-Zeitleiste',
'videoControls.volumeSlider' => 'Lautstärkepegel',
'videoControls.backButton' => 'Zurück',
'videoControls.pipFailed' => 'Bild-in-Bild konnte nicht gestartet werden',
'videoControls.pipErrors.androidVersion' => 'Erfordert Android 8.0 oder neuer',
'videoControls.pipErrors.permissionDisabled' => 'Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild',
'videoControls.pipErrors.notSupported' => 'Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht',
'videoControls.pipErrors.failed' => 'Bild-in-Bild konnte nicht gestartet werden',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'Ein Fehler ist aufgetreten: ${error}',
'userStatus.admin' => 'Eigentümer',
'userStatus.restricted' => 'Eingeschränkt',
'userStatus.protected' => 'Geschützt',
@@ -1444,14 +1466,14 @@ extension on TranslationsDe {
'watchTogether.joining' => 'Beitreten...',
'watchTogether.controlMode' => 'Steuerungsmodus',
'watchTogether.controlModeQuestion' => 'Wer kann die Wiedergabe steuern?',
_ => null,
} ?? switch (path) {
'watchTogether.hostOnly' => 'Nur Host',
'watchTogether.anyone' => 'Alle',
'watchTogether.hostingSession' => 'Sitzung Hosten',
'watchTogether.inSession' => 'In Sitzung',
'watchTogether.sessionCode' => 'Sitzungscode',
'watchTogether.hostControlsPlayback' => 'Host steuert die Wiedergabe',
_ => null,
} ?? switch (path) {
'watchTogether.anyoneCanControl' => 'Alle können die Wiedergabe steuern',
'watchTogether.hostControls' => 'Host steuert',
'watchTogether.anyoneControls' => 'Alle steuern',
+37 -2
View File
@@ -935,6 +935,11 @@ class TranslationsVideoControlsEn {
/// en: 'Back'
String get backButton => 'Back';
/// en: 'Picture-in-picture failed to start'
String get pipFailed => 'Picture-in-picture failed to start';
late final TranslationsVideoControlsPipErrorsEn pipErrors = TranslationsVideoControlsPipErrorsEn._(_root);
}
// Path: userStatus
@@ -1963,6 +1968,30 @@ class TranslationsHotkeysActionsEn {
String get subSeekPrev => 'Seek to Previous Subtitle';
}
// Path: videoControls.pipErrors
class TranslationsVideoControlsPipErrorsEn {
TranslationsVideoControlsPipErrorsEn._(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'Requires Android 8.0 or newer'
String get androidVersion => 'Requires Android 8.0 or newer';
/// en: 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture'
String get permissionDisabled => 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture';
/// en: 'Device doesn't support picture-in-picture mode'
String get notSupported => 'Device doesn\'t support picture-in-picture mode';
/// en: 'Picture-in-picture failed to start'
String get failed => 'Picture-in-picture failed to start';
/// en: 'An error occurred: ${error}'
String unknown({required Object error}) => 'An error occurred: ${error}';
}
// Path: libraries.tabs
class TranslationsLibrariesTabsEn {
TranslationsLibrariesTabsEn._(this._root);
@@ -2281,6 +2310,12 @@ extension on Translations {
'videoControls.timelineSlider' => 'Video timeline',
'videoControls.volumeSlider' => 'Volume level',
'videoControls.backButton' => 'Back',
'videoControls.pipFailed' => 'Picture-in-picture failed to start',
'videoControls.pipErrors.androidVersion' => 'Requires Android 8.0 or newer',
'videoControls.pipErrors.permissionDisabled' => 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture',
'videoControls.pipErrors.notSupported' => 'Device doesn\'t support picture-in-picture mode',
'videoControls.pipErrors.failed' => 'Picture-in-picture failed to start',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'An error occurred: ${error}',
'userStatus.admin' => 'Admin',
'userStatus.restricted' => 'Restricted',
'userStatus.protected' => 'Protected',
@@ -2525,14 +2560,14 @@ extension on Translations {
'watchTogether.watchingWithOthers' => 'Watching with others',
'watchTogether.endSession' => 'End Session',
'watchTogether.leaveSession' => 'Leave Session',
_ => null,
} ?? switch (path) {
'watchTogether.endSessionQuestion' => 'End Session?',
'watchTogether.leaveSessionQuestion' => 'Leave Session?',
'watchTogether.endSessionConfirm' => 'This will end the session for all participants.',
'watchTogether.leaveSessionConfirm' => 'You will be removed from the session.',
'watchTogether.endSessionConfirmOverlay' => 'This will end the watch session for all participants.',
'watchTogether.leaveSessionConfirmOverlay' => 'You will be disconnected from the watch session.',
_ => null,
} ?? switch (path) {
'watchTogether.end' => 'End',
'watchTogether.leave' => 'Leave',
'watchTogether.syncing' => 'Syncing...',
+24 -2
View File
@@ -441,6 +441,8 @@ class _TranslationsVideoControlsFr implements TranslationsVideoControlsEn {
@override String get timelineSlider => 'Timeline vidéo';
@override String get volumeSlider => 'Niveau sonore';
@override String get backButton => 'Retour';
@override String get pipFailed => 'Échec du démarrage du mode image dans l\'image';
@override late final _TranslationsVideoControlsPipErrorsFr pipErrors = _TranslationsVideoControlsPipErrorsFr._(_root);
}
// Path: userStatus
@@ -900,6 +902,20 @@ class _TranslationsHotkeysActionsFr implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => 'Rechercher le sous-titre précédent';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsFr implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsFr._(this._root);
final TranslationsFr _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Nécessite Android 8.0 ou plus récent';
@override String get permissionDisabled => 'L\'autorisation Image dans l\'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l\'image';
@override String get notSupported => 'Cet appareil ne prend pas en charge le mode image dans l\'image';
@override String get failed => 'Échec du démarrage du mode image dans l\'image';
@override String unknown({required Object error}) => 'Une erreur s\'est produite : ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsFr implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsFr._(this._root);
@@ -1198,6 +1214,12 @@ extension on TranslationsFr {
'videoControls.timelineSlider' => 'Timeline vidéo',
'videoControls.volumeSlider' => 'Niveau sonore',
'videoControls.backButton' => 'Retour',
'videoControls.pipFailed' => 'Échec du démarrage du mode image dans l\'image',
'videoControls.pipErrors.androidVersion' => 'Nécessite Android 8.0 ou plus récent',
'videoControls.pipErrors.permissionDisabled' => 'L\'autorisation Image dans l\'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l\'image',
'videoControls.pipErrors.notSupported' => 'Cet appareil ne prend pas en charge le mode image dans l\'image',
'videoControls.pipErrors.failed' => 'Échec du démarrage du mode image dans l\'image',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'Une erreur s\'est produite : ${error}',
'userStatus.admin' => 'Admin',
'userStatus.restricted' => 'Restreint',
'userStatus.protected' => 'Protégé',
@@ -1442,14 +1464,14 @@ extension on TranslationsFr {
'watchTogether.watchingWithOthers' => 'Regarder avec d\'autres personnes',
'watchTogether.endSession' => 'Fin de session',
'watchTogether.leaveSession' => 'Quitter la session',
_ => null,
} ?? switch (path) {
'watchTogether.endSessionQuestion' => 'Terminer la session ?',
'watchTogether.leaveSessionQuestion' => 'Quitter la session ?',
'watchTogether.endSessionConfirm' => 'Cela mettra fin à la session pour tous les participants.',
'watchTogether.leaveSessionConfirm' => 'Vous allez être déconnecté de la session.',
'watchTogether.endSessionConfirmOverlay' => 'Cela mettra fin à la session de visionnage pour tous les participants.',
'watchTogether.leaveSessionConfirmOverlay' => 'Vous serez déconnecté de la session de visionnage.',
_ => null,
} ?? switch (path) {
'watchTogether.end' => 'Terminer',
'watchTogether.leave' => 'Fin',
'watchTogether.syncing' => 'Synchronisation...',
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsIt implements TranslationsVideoControlsEn {
@override String get timelineSlider => 'Timeline video';
@override String get volumeSlider => 'Livello volume';
@override String get backButton => 'Indietro';
@override String get pipFailed => 'Impossibile avviare la modalità Picture-in-Picture';
@override late final _TranslationsVideoControlsPipErrorsIt pipErrors = _TranslationsVideoControlsPipErrorsIt._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsIt implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => 'Vai al sottotitolo precedente';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsIt implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsIt._(this._root);
final TranslationsIt _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Richiede Android 8.0 o versioni successive';
@override String get permissionDisabled => 'L\'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture';
@override String get notSupported => 'Questo dispositivo non supporta la modalità Picture-in-Picture';
@override String get failed => 'Impossibile avviare la modalità Picture-in-Picture';
@override String unknown({required Object error}) => 'Si è verificato un errore: ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsIt implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsIt._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsIt {
'videoControls.timelineSlider' => 'Timeline video',
'videoControls.volumeSlider' => 'Livello volume',
'videoControls.backButton' => 'Indietro',
'videoControls.pipFailed' => 'Impossibile avviare la modalità Picture-in-Picture',
'videoControls.pipErrors.androidVersion' => 'Richiede Android 8.0 o versioni successive',
'videoControls.pipErrors.permissionDisabled' => 'L\'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture',
'videoControls.pipErrors.notSupported' => 'Questo dispositivo non supporta la modalità Picture-in-Picture',
'videoControls.pipErrors.failed' => 'Impossibile avviare la modalità Picture-in-Picture',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'Si è verificato un errore: ${error}',
'userStatus.admin' => 'Admin',
'userStatus.restricted' => 'Limitato',
'userStatus.protected' => 'Protetto',
@@ -1444,14 +1466,14 @@ extension on TranslationsIt {
'watchTogether.joining' => 'Connessione...',
'watchTogether.controlMode' => 'Modalità di Controllo',
'watchTogether.controlModeQuestion' => 'Chi può controllare la riproduzione?',
_ => null,
} ?? switch (path) {
'watchTogether.hostOnly' => 'Solo Host',
'watchTogether.anyone' => 'Tutti',
'watchTogether.hostingSession' => 'Hosting Sessione',
'watchTogether.inSession' => 'In Sessione',
'watchTogether.sessionCode' => 'Codice Sessione',
'watchTogether.hostControlsPlayback' => 'L\'host controlla la riproduzione',
_ => null,
} ?? switch (path) {
'watchTogether.anyoneCanControl' => 'Tutti possono controllare la riproduzione',
'watchTogether.hostControls' => 'Controllo host',
'watchTogether.anyoneControls' => 'Controllo di tutti',
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsKo implements TranslationsVideoControlsEn {
@override String get timelineSlider => '타임라인';
@override String get volumeSlider => '볼륨 조절';
@override String get backButton => '뒤로 가기';
@override String get pipFailed => '화면 속 화면 모드를 시작할 수 없습니다';
@override late final _TranslationsVideoControlsPipErrorsKo pipErrors = _TranslationsVideoControlsPipErrorsKo._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsKo implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => '이전 자막으로 이동';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsKo implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsKo._(this._root);
final TranslationsKo _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Android 8.0 이상이 필요합니다';
@override String get permissionDisabled => '화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요';
@override String get notSupported => '이 기기는 화면 속 화면 모드를 지원하지 않습니다';
@override String get failed => '화면 속 화면 모드를 시작할 수 없습니다';
@override String unknown({required Object error}) => '오류가 발생했습니다: ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsKo implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsKo._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsKo {
'videoControls.timelineSlider' => '타임라인',
'videoControls.volumeSlider' => '볼륨 조절',
'videoControls.backButton' => '뒤로 가기',
'videoControls.pipFailed' => '화면 속 화면 모드를 시작할 수 없습니다',
'videoControls.pipErrors.androidVersion' => 'Android 8.0 이상이 필요합니다',
'videoControls.pipErrors.permissionDisabled' => '화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요',
'videoControls.pipErrors.notSupported' => '이 기기는 화면 속 화면 모드를 지원하지 않습니다',
'videoControls.pipErrors.failed' => '화면 속 화면 모드를 시작할 수 없습니다',
'videoControls.pipErrors.unknown' => ({required Object error}) => '오류가 발생했습니다: ${error}',
'userStatus.admin' => '관리자',
'userStatus.restricted' => '제한됨',
'userStatus.protected' => '보호됨',
@@ -1444,14 +1466,14 @@ extension on TranslationsKo {
'watchTogether.hostBadge' => '호스트',
'watchTogether.youAreHost' => '당신은 호스트 입니다',
'watchTogether.watchingWithOthers' => '다른 사람과 함께 시청 중',
_ => null,
} ?? switch (path) {
'watchTogether.endSession' => '세션 종료',
'watchTogether.leaveSession' => '세션 탈퇴',
'watchTogether.endSessionQuestion' => '세션을 종료 하시겠습니까?',
'watchTogether.leaveSessionQuestion' => '세션을 탈퇴 하시겠습니까?',
'watchTogether.endSessionConfirm' => '이 작업은 모든 참가자의 세션을 종료합니다.',
'watchTogether.leaveSessionConfirm' => '당신은 세션에서 제거됩니다.',
_ => null,
} ?? switch (path) {
'watchTogether.endSessionConfirmOverlay' => '이것은 모든 참가자의 시청 세션을 종료합니다.',
'watchTogether.leaveSessionConfirmOverlay' => '시청 세션 연결이 끊어집니다.',
'watchTogether.end' => '종료',
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsNl implements TranslationsVideoControlsEn {
@override String get timelineSlider => 'Videotijdlijn';
@override String get volumeSlider => 'Volumeniveau';
@override String get backButton => 'Terug';
@override String get pipFailed => 'Beeld-in-beeld kon niet worden gestart';
@override late final _TranslationsVideoControlsPipErrorsNl pipErrors = _TranslationsVideoControlsPipErrorsNl._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsNl implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => 'Naar vorige ondertitel';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsNl implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsNl._(this._root);
final TranslationsNl _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Vereist Android 8.0 of nieuwer';
@override String get permissionDisabled => 'Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld';
@override String get notSupported => 'Dit apparaat ondersteunt geen beeld-in-beeld modus';
@override String get failed => 'Beeld-in-beeld kon niet worden gestart';
@override String unknown({required Object error}) => 'Er is een fout opgetreden: ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsNl implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsNl._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsNl {
'videoControls.timelineSlider' => 'Videotijdlijn',
'videoControls.volumeSlider' => 'Volumeniveau',
'videoControls.backButton' => 'Terug',
'videoControls.pipFailed' => 'Beeld-in-beeld kon niet worden gestart',
'videoControls.pipErrors.androidVersion' => 'Vereist Android 8.0 of nieuwer',
'videoControls.pipErrors.permissionDisabled' => 'Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld',
'videoControls.pipErrors.notSupported' => 'Dit apparaat ondersteunt geen beeld-in-beeld modus',
'videoControls.pipErrors.failed' => 'Beeld-in-beeld kon niet worden gestart',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'Er is een fout opgetreden: ${error}',
'userStatus.admin' => 'Beheerder',
'userStatus.restricted' => 'Beperkt',
'userStatus.protected' => 'Beschermd',
@@ -1444,14 +1466,14 @@ extension on TranslationsNl {
'watchTogether.joining' => 'Deelnemen...',
'watchTogether.controlMode' => 'Controlemodus',
'watchTogether.controlModeQuestion' => 'Wie kan het afspelen bedienen?',
_ => null,
} ?? switch (path) {
'watchTogether.hostOnly' => 'Alleen Host',
'watchTogether.anyone' => 'Iedereen',
'watchTogether.hostingSession' => 'Sessie Hosten',
'watchTogether.inSession' => 'In Sessie',
'watchTogether.sessionCode' => 'Sessiecode',
'watchTogether.hostControlsPlayback' => 'Host bedient het afspelen',
_ => null,
} ?? switch (path) {
'watchTogether.anyoneCanControl' => 'Iedereen kan het afspelen bedienen',
'watchTogether.hostControls' => 'Host bedient',
'watchTogether.anyoneControls' => 'Iedereen bedient',
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsSv implements TranslationsVideoControlsEn {
@override String get timelineSlider => 'Videotidslinje';
@override String get volumeSlider => 'Volymnivå';
@override String get backButton => 'Tillbaka';
@override String get pipFailed => 'Bild-i-bild kunde inte starta';
@override late final _TranslationsVideoControlsPipErrorsSv pipErrors = _TranslationsVideoControlsPipErrorsSv._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsSv implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => 'Hoppa till föregående undertext';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsSv implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsSv._(this._root);
final TranslationsSv _root; // ignore: unused_field
// Translations
@override String get androidVersion => 'Kräver Android 8.0 eller nyare';
@override String get permissionDisabled => 'Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild';
@override String get notSupported => 'Denna enhet stöder inte bild-i-bild-läge';
@override String get failed => 'Bild-i-bild kunde inte starta';
@override String unknown({required Object error}) => 'Ett fel uppstod: ${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsSv implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsSv._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsSv {
'videoControls.timelineSlider' => 'Videotidslinje',
'videoControls.volumeSlider' => 'Volymnivå',
'videoControls.backButton' => 'Tillbaka',
'videoControls.pipFailed' => 'Bild-i-bild kunde inte starta',
'videoControls.pipErrors.androidVersion' => 'Kräver Android 8.0 eller nyare',
'videoControls.pipErrors.permissionDisabled' => 'Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild',
'videoControls.pipErrors.notSupported' => 'Denna enhet stöder inte bild-i-bild-läge',
'videoControls.pipErrors.failed' => 'Bild-i-bild kunde inte starta',
'videoControls.pipErrors.unknown' => ({required Object error}) => 'Ett fel uppstod: ${error}',
'userStatus.admin' => 'Admin',
'userStatus.restricted' => 'Begränsad',
'userStatus.protected' => 'Skyddad',
@@ -1444,14 +1466,14 @@ extension on TranslationsSv {
'watchTogether.joining' => 'Ansluter...',
'watchTogether.controlMode' => 'Kontrollläge',
'watchTogether.controlModeQuestion' => 'Vem kan styra uppspelningen?',
_ => null,
} ?? switch (path) {
'watchTogether.hostOnly' => 'Endast Värd',
'watchTogether.anyone' => 'Alla',
'watchTogether.hostingSession' => 'Värd för Session',
'watchTogether.inSession' => 'I Session',
'watchTogether.sessionCode' => 'Sessionskod',
'watchTogether.hostControlsPlayback' => 'Värden styr uppspelningen',
_ => null,
} ?? switch (path) {
'watchTogether.anyoneCanControl' => 'Alla kan styra uppspelningen',
'watchTogether.hostControls' => 'Värd styr',
'watchTogether.anyoneControls' => 'Alla styr',
+24 -2
View File
@@ -442,6 +442,8 @@ class _TranslationsVideoControlsZh implements TranslationsVideoControlsEn {
@override String get timelineSlider => '视频时间轴';
@override String get volumeSlider => '音量调节';
@override String get backButton => '返回';
@override String get pipFailed => '画中画启动失败';
@override late final _TranslationsVideoControlsPipErrorsZh pipErrors = _TranslationsVideoControlsPipErrorsZh._(_root);
}
// Path: userStatus
@@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsZh implements TranslationsHotkeysActionsEn {
@override String get subSeekPrev => '跳转到上一字幕';
}
// Path: videoControls.pipErrors
class _TranslationsVideoControlsPipErrorsZh implements TranslationsVideoControlsPipErrorsEn {
_TranslationsVideoControlsPipErrorsZh._(this._root);
final TranslationsZh _root; // ignore: unused_field
// Translations
@override String get androidVersion => '需要 Android 8.0 或更高版本';
@override String get permissionDisabled => '画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用';
@override String get notSupported => '此设备不支持画中画模式';
@override String get failed => '画中画启动失败';
@override String unknown({required Object error}) => '发生错误:${error}';
}
// Path: libraries.tabs
class _TranslationsLibrariesTabsZh implements TranslationsLibrariesTabsEn {
_TranslationsLibrariesTabsZh._(this._root);
@@ -1201,6 +1217,12 @@ extension on TranslationsZh {
'videoControls.timelineSlider' => '视频时间轴',
'videoControls.volumeSlider' => '音量调节',
'videoControls.backButton' => '返回',
'videoControls.pipFailed' => '画中画启动失败',
'videoControls.pipErrors.androidVersion' => '需要 Android 8.0 或更高版本',
'videoControls.pipErrors.permissionDisabled' => '画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用',
'videoControls.pipErrors.notSupported' => '此设备不支持画中画模式',
'videoControls.pipErrors.failed' => '画中画启动失败',
'videoControls.pipErrors.unknown' => ({required Object error}) => '发生错误:${error}',
'userStatus.admin' => '管理员',
'userStatus.restricted' => '受限',
'userStatus.protected' => '受保护',
@@ -1444,14 +1466,14 @@ extension on TranslationsZh {
'watchTogether.joining' => '加入中...',
'watchTogether.controlMode' => '控制模式',
'watchTogether.controlModeQuestion' => '谁可以控制播放?',
_ => null,
} ?? switch (path) {
'watchTogether.hostOnly' => '仅主持人',
'watchTogether.anyone' => '任何人',
'watchTogether.hostingSession' => '主持会话',
'watchTogether.inSession' => '在会话中',
'watchTogether.sessionCode' => '会话代码',
'watchTogether.hostControlsPlayback' => '主持人控制播放',
_ => null,
} ?? switch (path) {
'watchTogether.anyoneCanControl' => '任何人都可以控制播放',
'watchTogether.hostControls' => '主持人控制',
'watchTogether.anyoneControls' => '任何人控制',
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "Rotationslås",
"timelineSlider": "Videotidslinje",
"volumeSlider": "Volymnivå",
"backButton": "Tillbaka"
"backButton": "Tillbaka",
"pipFailed": "Bild-i-bild kunde inte starta",
"pipErrors": {
"androidVersion": "Kräver Android 8.0 eller nyare",
"permissionDisabled": "Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild",
"notSupported": "Denna enhet stöder inte bild-i-bild-läge",
"failed": "Bild-i-bild kunde inte starta",
"unknown": "Ett fel uppstod: ${error}"
}
},
"userStatus": {
"admin": "Admin",
+9 -1
View File
@@ -290,7 +290,15 @@
"rotationLockButton": "旋转锁定",
"timelineSlider": "视频时间轴",
"volumeSlider": "音量调节",
"backButton": "返回"
"backButton": "返回",
"pipFailed": "画中画启动失败",
"pipErrors": {
"androidVersion": "需要 Android 8.0 或更高版本",
"permissionDisabled": "画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用",
"notSupported": "此设备不支持画中画模式",
"failed": "画中画启动失败",
"unknown": "发生错误:${error}"
}
},
"userStatus": {
"admin": "管理员",
+5 -4
View File
@@ -858,10 +858,11 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
);
}
void _togglePIPMode() {
setState(() {
_videoPIPManager?.togglePIP();
});
Future<void> _togglePIPMode() async {
final result = await _videoPIPManager?.togglePIP();
if (result != null && !result.$1 && mounted) {
showErrorSnackBar(context, result.$2 ?? t.videoControls.pipFailed);
}
}
/// Cycle through BoxFit modes: contain → cover → fill → contain (for button)
+21 -2
View File
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:plezy/i18n/strings.g.dart';
class PipService {
static const MethodChannel _channel = MethodChannel('app.plezy/pip');
@@ -29,7 +30,25 @@ class PipService {
return await _channel.invokeMethod<bool>('isSupported') ?? false;
}
static Future<void> enter({int? width, int? height}) async {
await _channel.invokeMethod('enter', {'width': width, 'height': height});
static Future<(bool success, String? error)> enter({int? width, int? height}) async {
final result = await _channel.invokeMethod<Map>('enter', {'width': width, 'height': height});
if (result == null) {
return (false, t.videoControls.pipErrors.unknown(error: 'No response'));
}
final success = result['success'] as bool? ?? false;
final errorCode = result['errorCode'] as String?;
final errorMessage = result['errorMessage'] as String?;
final error = errorCode != null ? _getLocalizedError(errorCode, errorMessage) : null;
return (success, error);
}
static String _getLocalizedError(String errorCode, String? errorMessage) {
return switch (errorCode) {
'android_version' => t.videoControls.pipErrors.androidVersion,
'permission_disabled' => t.videoControls.pipErrors.permissionDisabled,
'not_supported' => t.videoControls.pipErrors.notSupported,
'failed' => t.videoControls.pipErrors.failed,
_ => t.videoControls.pipErrors.unknown(error: errorMessage ?? 'Unknown error'),
};
}
}
+4 -4
View File
@@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../mpv/mpv.dart';
import '../services/pip_service.dart';
@@ -16,9 +15,10 @@ class VideoPIPManager {
ValueNotifier<bool> get isPipActive => PipService().isPipActive;
/// Toggle native PiP
Future<void> togglePIP() async {
/// Returns a tuple of (success, error message) for error handling
Future<(bool success, String? error)> togglePIP() async {
final supported = await PipService.isSupported();
if (!supported) return;
if (!supported) return (false, 'PiP not supported on this device');
// Try to get actual video dimensions from MPV
int? width;
@@ -39,6 +39,6 @@ class VideoPIPManager {
width ??= _playerSize?.width.toInt();
height ??= _playerSize?.height.toInt();
await PipService.enter(width: width, height: height);
return await PipService.enter(width: width, height: height);
}
}