I'm currently developing an Android application using LibVLC for video playback. However, I'm encountering an issue where LibVLC is unable to open the media resource locator (MRL) when attempting to play an MP4 video from internal storage.
I'm using Android 13 as the target SDK. Here's the relevant code snippet:
// Code snippet
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.view.WindowManager;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IVLCVout;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import org.videolan.libvlc.Media;
public class MainActivity extends AppCompatActivity {
private SurfaceView surfaceView;
private LibVLC libVLC;
private org.videolan.libvlc.MediaPlayer mediaPlayer;
private Button browseButton;
private static final int PERMISSION_REQUEST_CODE = 1;
private static final int PICK_VIDEO_REQUEST_CODE = 2;
private static WeakReference<MainActivity> instance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = new WeakReference<>(this);
setContentView(R.layout.activity_main);
checkPermissions(); // Call checkPermissions() in onCreate()
// Prevent screenshots while the video is playing
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
// Check if USB debugging is enabled
// if (isUsbDebuggingEnabled()) {
// showUsbDebuggingDialog();
// }
if (RootChecker.isDeviceRooted()) {
showRootedDeviceDialog();
return; // Exit the onCreate method if the device is rooted
}
// Check if the app is running on an emulator
if (EmulatorDetector.isEmulator()) {
showEmulatorDetectedDialog();
return; // Exit the onCreate method if running on an emulator
}
// Check app integrity
// if (!AppIntegrityChecker.isSignatureValid(this)) {
// Show error dialog and redirect to Play Store
// showAppCorruptDialog();
// return; // Exit the onCreate method if app integrity check fails
// }
// Initialize VLC
ArrayList<String> options = new ArrayList<>();
options.add("--aout=opensles");
options.add("--audio-time-stretch"); // time stretching
options.add("-vvv"); // verbosity
libVLC = new LibVLC(this, options);
// Initialize media player
mediaPlayer = new MediaPlayer(libVLC);
mediaPlayer.setEventListener(event -> {
switch (event.type) {
case MediaPlayer.Event.EndReached:
// Handle the end of media playback
break;
}
});
// Initialize player view
surfaceView = findViewById(R.id.surface_view);
IVLCVout vout = mediaPlayer.getVLCVout();
vout.setVideoView(surfaceView);
vout.attachViews();
// Set up browse button click listener
browseButton = findViewById(R.id.browse_button);
browseButton.setOnClickListener(v -> pickVideo());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_open:
// Launch file picker activity to select video file
pickVideo();
return true;
case R.id.action_add:
// Handle "Add License" menu item click
openAddLicenseActivity();
return true;
case R.id.action_edit:
// Handle "Edit License" menu item click
openEditLicenseActivity();
return true;
case R.id.action_exit:
// Handle "Exit" menu item click
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void openAddLicenseActivity() {
Intent intent = new Intent(this, AddLicense.class);
startActivity(intent);
}
private void openEditLicenseActivity() {
Intent intent = new Intent(this, EditLicense.class);
startActivity(intent);
}
@Override
protected void onResume() {
super.onResume();
checkPermissions(); // Call checkPermissions() in onResume() too
}
private void checkPermissions() {
String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
if (!arePermissionsGranted(permissions)) {
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
}
}
private void showAppCorruptDialog() {
new AlertDialog.Builder(this)
.setTitle("App Corrupted")
.setMessage("Please download the app from the Google Play Store.")
.setPositiveButton("OK", (dialog, which) -> redirectToPlayStore())
.setCancelable(false)
.show();
}
private void redirectToPlayStore() {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.dedseec")));
} catch (android.content.ActivityNotFoundException e) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.dedseec")));
}
finish(); // Close the current activity
}
private void showRootedDeviceDialog() {
new AlertDialog.Builder(this)
.setTitle("Rooted Device Detected")
.setMessage("This app cannot be used on rooted devices.")
.setPositiveButton("Exit", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
private void showEmulatorDetectedDialog() {
new AlertDialog.Builder(this)
.setTitle("Emulator Detected")
.setMessage("This app cannot be used on emulators.")
.setPositiveButton("Exit", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
private void pickVideo() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("video/*");
startActivityForResult(intent, PICK_VIDEO_REQUEST_CODE);
}
private void onVideoPicked(Uri videoUri) {
if (videoUri != null) {
playVideo(videoUri);
}
}
private void playVideo(Uri videoUri) {
// Create media object
Media media = new Media(libVLC, videoUri);
// Set media to the media player
mediaPlayer.setMedia(media);
// Start playback
mediaPlayer.play();
}
// Method to get the context of MainActivity
public static Context getContext() {
return instance().getApplicationContext();
}
// Method to read data from a file
public static String readFromFile(File file) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
int size = fis.available();
byte[] buffer = new byte[size];
fis.read(buffer);
return new String(buffer);
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// Method to write data to a file
public static void writeToFile(File file, String data) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// Helper method to get the instance of MainActivity
private static MainActivity instance() {
return instance != null ? instance.get() : null;
}
private boolean isUsbDebuggingEnabled() {
int adbEnabled = Settings.Global.getInt(getContentResolver(), Settings.Global.ADB_ENABLED, 0);
return adbEnabled == 1;
}
private void showUsbDebuggingDialog() {
new AlertDialog.Builder(this)
.setTitle("USB Debugging Enabled")
.setMessage("Please disable USB debugging to continue using the app.")
.setPositiveButton("OK", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
// Permission granted, proceed with your action
} else {
// Permission denied, handle accordingly (e.g., show a message)
Toast.makeText(this, permission + " permission denied", Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_VIDEO_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
Uri videoUri = data.getData();
if (videoUri != null) {
if (isDedFile(videoUri)) {
onVideoPicked(videoUri);
} else {
// Show error message for invalid file format
Toast.makeText(this, "Invalid file format. Please select a .ded file.", Toast.LENGTH_SHORT).show();
}
}
}
}
private boolean isDedFile(Uri uri) {
// Always return true to allow playing any video file
return true;
}
private String getFileExtension(Uri uri) {
// Get the file path from the URI
String filePath = getFilePathFromUri(uri);
// Extract the file extension
if (filePath != null && !filePath.isEmpty()) {
int lastDotIndex = filePath.lastIndexOf('.');
if (lastDotIndex != -1) {
return filePath.substring(lastDotIndex + 1);
}
}
return null;
}
private String getFilePathFromUri(Uri uri) {
String filePath = null;
if (uri != null) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
filePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
return filePath;
}
private boolean arePermissionsGranted(String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
private byte[] decrypt(byte[] value, String key) {
try {
return Decryptor.decrypt(value, key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mediaPlayer.release();
libVLC.release();
}
}
The error message I'm receiving is:
libvlc input: VLC is unable to open the MRL 'content://com.android.externalstorage.documents/document/WA0033.mp4'.