fix: external player on Android 11+
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user