fix: external player on Android 11+

This commit is contained in:
edde746
2026-02-14 20:19:27 +01:00
parent fb9a1cd77e
commit 0d72c6c1a4
4 changed files with 44 additions and 27 deletions
+5
View File
@@ -111,5 +111,10 @@
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<!-- Required to resolve external video player apps on Android 11+ -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="video/*" />
</intent>
</queries>
</manifest>
@@ -82,25 +82,37 @@ class MainActivity : FlutterActivity() {
}
try {
val uri: Uri = if (filePath.startsWith("content://")) {
Uri.parse(filePath)
val uri: Uri
val grantRead: Boolean
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
uri = Uri.parse(filePath)
grantRead = false
} else if (filePath.startsWith("content://")) {
uri = Uri.parse(filePath)
grantRead = true
} else {
// Convert file path to content:// URI via FileProvider
val path = if (filePath.startsWith("file://")) filePath.removePrefix("file://") else filePath
FileProvider.getUriForFile(this, "com.edde746.plezy.fileprovider", File(path))
uri = FileProvider.getUriForFile(this, "com.edde746.plezy.fileprovider", File(path))
grantRead = true
}
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "video/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (grantRead) {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if (packageName != null) {
setPackage(packageName)
}
}
startActivity(intent)
result.success(true)
} catch (e: android.content.ActivityNotFoundException) {
result.error("APP_NOT_FOUND", "No app found for package: $packageName", null)
} catch (e: Exception) {
result.success(false)
result.error("LAUNCH_FAILED", e.message ?: e.javaClass.simpleName, null)
}
}
else -> result.notImplemented()
@@ -4,4 +4,11 @@
<files-path name="app_files" path="." />
<!-- External files directory (fallback) -->
<external-files-path name="external_files" path="." />
<!-- External storage root (for custom download paths on shared storage) -->
<external-path name="external" path="." />
<!-- Cache directories -->
<cache-path name="cache" path="." />
<external-cache-path name="external_cache" path="." />
<!-- Filesystem root (ultimate fallback for any absolute path) -->
<root-path name="root" path="" />
</paths>
+14 -21
View File
@@ -49,11 +49,9 @@ class ExternalPlayerService {
final settings = await SettingsService.getInstance();
final player = settings.getSelectedExternalPlayer();
appLogger.d('Launching external player: ${player.name} with URL: $resolvedUrl');
// On Android, use native intent for local files (file:// and content://)
if (Platform.isAndroid && _isLocalUrl(resolvedUrl)) {
return _launchAndroidLocalFile(resolvedUrl, player, context);
// On Android, always use native intent to avoid url_launcher opening in browser
if (Platform.isAndroid) {
return _launchAndroidNative(resolvedUrl, player, context);
}
final launched = await player.launch(resolvedUrl);
@@ -70,27 +68,22 @@ class ExternalPlayerService {
}
}
static bool _isLocalUrl(String url) {
return url.startsWith('file://') || url.startsWith('content://') || url.startsWith('/');
}
/// Launch a local video file on Android using native ACTION_VIEW intent
/// with FileProvider content:// URI and FLAG_GRANT_READ_URI_PERMISSION.
static Future<bool> _launchAndroidLocalFile(String url, ExternalPlayer player, BuildContext context) async {
/// Launch a video on Android using native ACTION_VIEW intent.
/// Handles local files (file://, content://, absolute paths) and remote URLs.
static Future<bool> _launchAndroidNative(String url, ExternalPlayer player, BuildContext context) async {
try {
final filePath = url.startsWith('file://') ? url.substring(7) : url;
final result = await _externalPlayerChannel.invokeMethod<bool>('openVideo', {
'filePath': filePath,
await _externalPlayerChannel.invokeMethod<bool>('openVideo', {
'filePath': url,
if (player.id != 'system_default') 'package': _getAndroidPackage(player),
});
final launched = result ?? false;
if (!launched && context.mounted) {
return true;
} on PlatformException catch (e) {
if (e.code == 'APP_NOT_FOUND' && context.mounted) {
showErrorSnackBar(context, t.externalPlayer.appNotInstalled(name: player.name));
} else if (context.mounted) {
showErrorSnackBar(context, t.externalPlayer.launchFailed);
}
return launched;
} catch (e) {
appLogger.w('Android native intent failed, falling back to player.launch', error: e);
return player.launch(url);
return false;
}
}