Skip to content

Commit

Permalink
Merge pull request #10 from eduardoRoth/feat/v2.1.0
Browse files Browse the repository at this point in the history
Feat/v2.1.0
  • Loading branch information
eduardoRoth authored Jan 6, 2025
2 parents 67643c4 + 0a8cdaa commit f3e221d
Show file tree
Hide file tree
Showing 19 changed files with 594 additions and 720 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ npm install @eduardoroth/media-player
npx cap sync
```

## Android

For the plugin to work correctly, you have to set/add in your `AndroidManifest.xml` for your main activity the following setting for `android:configChanges`

```xml
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|screenLayout|uiMode|fontScale|density|fontWeightAdjustment"
>
</activity>
```

This will make sure the app doesn't break when doing any of the following actions:

- Changing the orientation
- Showing or hiding the keyboard
- Changing the screen size
- Casting or mirroring the screen
- Changing the font scale
- Changing the screen density

## API

<docgen-index>
Expand Down Expand Up @@ -504,7 +524,7 @@ removeAllListeners(options: MediaPlayerIdOptions) => Promise<void>

#### MediaPlayerAndroidOptions

<code>{ enableChromecast?: boolean; enablePiP?: boolean; enableBackgroundPlay?: boolean; openInFullscreen?: boolean; automaticallyEnterPiP?: boolean; fullscreenOnLandscape?: boolean; top?: number; start?: number; height?: number; width?: number; }</code>
<code>{ enableChromecast?: boolean; enablePiP?: boolean; enableBackgroundPlay?: boolean; openInFullscreen?: boolean; automaticallyEnterPiP?: boolean; fullscreenOnLandscape?: boolean; stopOnTaskRemoved?: boolean; top?: number; start?: number; height?: number; width?: number; }</code>


#### MediaPlayerWebOptions
Expand Down
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.7'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7'
implementation 'androidx.lifecycle:lifecycle-service:2.8.7'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
28 changes: 27 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<application
android:appCategory="video"
android:enableOnBackInvokedCallback="true"
tools:ignore="UnusedAttribute" />
tools:ignore="UnusedAttribute">

<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/auto_app_desc" />
<service
android:name=".MediaPlayerService"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
android:permission="FOREGROUND_SERVICE_MEDIA_PLAYBACK">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="android.media.browse.MediaBrowserService" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
</intent-filter>
</service>
<receiver
android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
100 changes: 62 additions & 38 deletions android/src/main/java/dev/eduardoroth/mediaplayer/MediaPlayer.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package dev.eduardoroth.mediaplayer;

import android.content.ComponentName;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;

import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;

Expand All @@ -18,7 +24,6 @@
import dev.eduardoroth.mediaplayer.state.MediaPlayerState;
import dev.eduardoroth.mediaplayer.state.MediaPlayerStateProvider;

@UnstableApi
public class MediaPlayer {
public static long VIDEO_STEP = 10000;
private final AppCompatActivity _currentActivity;
Expand All @@ -27,30 +32,47 @@ public class MediaPlayer {
_currentActivity = currentActivity;
}

@UnstableApi
@OptIn(markerClass = UnstableApi.class)
public void create(PluginCall call, String playerId, String url, AndroidOptions android, ExtraOptions extra) {
JSObject ret = new JSObject();
ret.put("method", "create");
try {
MediaPlayerStateProvider.getState(playerId);
ret.put("result", false);
ret.put("message", "Player with id " + playerId + " is already created");
} catch (Error err) {
extra.poster = extra.poster != null ? getFinalPath(extra.poster) : null;
MediaPlayerContainer playerContainer = new MediaPlayerContainer(_currentActivity, getFinalPath(url), playerId, android, extra);
_currentActivity.getSupportFragmentManager().beginTransaction().add(R.id.MediaPlayerFragmentContainerView, playerContainer, playerId).commit();
ret.put("result", true);
ret.put("value", playerId);
}
call.resolve(ret);
Bundle connectionHints = new Bundle();
connectionHints.putString("playerId", playerId);
connectionHints.putString("videoUrl", url);
connectionHints.putSerializable("android", android);
extra.poster = extra.poster != null ? getFinalPath(extra.poster) : null;
connectionHints.putSerializable("extra", extra);

SessionToken sessionToken = new SessionToken(
_currentActivity.getApplicationContext(),
new ComponentName(_currentActivity.getApplicationContext(), MediaPlayerService.class)
);
ListenableFuture<MediaController> futureController = new MediaController
.Builder(_currentActivity.getApplicationContext(), sessionToken)
.setConnectionHints(connectionHints)
.buildAsync();

futureController.addListener(() -> {
JSObject ret = new JSObject();
ret.put("method", "create");
try {
_currentActivity.getSupportFragmentManager().beginTransaction().add(R.id.MediaPlayerFragmentContainerView, new MediaPlayerContainer(futureController.get(), playerId), playerId).commit();
ret.put("result", true);
ret.put("value", playerId);
call.resolve(ret);
} catch (Exception | Error futureError) {
ret.put("result", false);
ret.put("message", "An error occurred while creating player with id " + playerId);
Log.e("MEDIA PLAYER ROTH", futureError.toString());
call.resolve(ret);
}
}, _currentActivity.getMainExecutor());
}

public void play(PluginCall call, String playerId) {
JSObject ret = new JSObject();
ret.put("method", "play");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().play();
playerState.mediaController.get().play();
ret.put("result", true);
ret.put("value", true);
} catch (Error err) {
Expand All @@ -65,7 +87,7 @@ public void pause(PluginCall call, String playerId) {
ret.put("method", "pause");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().pause();
playerState.mediaController.get().pause();
ret.put("result", true);
ret.put("value", true);
} catch (Error err) {
Expand All @@ -80,7 +102,7 @@ public void getDuration(PluginCall call, String playerId) {
ret.put("method", "getDuration");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
long duration = playerState.playerController.get().getDuration();
long duration = playerState.mediaController.get().getDuration();
ret.put("result", true);
ret.put("value", duration == C.TIME_UNSET ? 0 : (duration / 1000));
} catch (Error err) {
Expand All @@ -95,7 +117,7 @@ public void getCurrentTime(PluginCall call, String playerId) {
ret.put("method", "getCurrentTime");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
long currentTime = playerState.playerController.get().getCurrentTime();
long currentTime = playerState.mediaController.get().getCurrentPosition();
ret.put("result", true);
ret.put("value", currentTime == C.TIME_UNSET ? 0 : (currentTime / 1000));
} catch (Error err) {
Expand All @@ -109,10 +131,14 @@ public void setCurrentTime(PluginCall call, String playerId, long time) {
JSObject ret = new JSObject();
ret.put("method", "setCurrentTime");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
long updatedTime = playerState.playerController.get().setCurrentTime(time);
MediaController controller = MediaPlayerStateProvider.getState(playerId).mediaController.get();

long duration = controller.getDuration();
long currentTime = controller.getCurrentPosition();
long seekPosition = currentTime == C.TIME_UNSET ? 0 : Math.min(Math.max(0, time * 1000), duration == C.TIME_UNSET ? 0 : duration);
controller.seekTo(seekPosition);
ret.put("result", true);
ret.put("value", updatedTime);
ret.put("value", seekPosition);
} catch (Error err) {
ret.put("result", false);
ret.put("message", "Player not found");
Expand All @@ -126,7 +152,7 @@ public void isPlaying(PluginCall call, String playerId) {
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
ret.put("result", true);
ret.put("value", playerState.playerController.get().isPlaying());
ret.put("value", playerState.mediaController.get().isPlaying());
} catch (Error err) {
ret.put("result", false);
ret.put("message", "Player not found");
Expand All @@ -140,7 +166,7 @@ public void isMuted(PluginCall call, String playerId) {
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
ret.put("result", true);
ret.put("value", playerState.playerController.get().isMuted());
ret.put("value", playerState.mediaController.get().getVolume() == 0);
} catch (Error err) {
ret.put("result", false);
ret.put("message", "Player not found");
Expand All @@ -153,7 +179,7 @@ public void mute(PluginCall call, String playerId) {
ret.put("method", "mute");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().mute();
playerState.mediaController.get().setVolume(0);
ret.put("result", true);
ret.put("value", true);
} catch (Error err) {
Expand All @@ -169,7 +195,7 @@ public void getVolume(PluginCall call, String playerId) {
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
ret.put("result", true);
ret.put("value", playerState.playerController.get().getVolume());
ret.put("value", playerState.mediaController.get().getVolume());
} catch (Error err) {
ret.put("result", false);
ret.put("message", "Player not found");
Expand All @@ -182,7 +208,7 @@ public void setVolume(PluginCall call, String playerId, Double volume) {
ret.put("method", "setVolume");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().setVolume(volume.floatValue());
playerState.mediaController.get().setVolume(volume.floatValue());
ret.put("result", true);
ret.put("value", volume);
} catch (Error err) {
Expand All @@ -198,7 +224,7 @@ public void getRate(PluginCall call, String playerId) {
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
ret.put("result", true);
ret.put("value", playerState.playerController.get().getRate());
ret.put("value", playerState.mediaController.get().getPlaybackParameters().speed);
} catch (Error err) {
ret.put("result", false);
ret.put("message", "Player not found");
Expand All @@ -211,7 +237,7 @@ public void setRate(PluginCall call, String playerId, Double rate) {
ret.put("method", "setRate");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().setRate(rate.floatValue());
playerState.mediaController.get().setPlaybackSpeed(rate.floatValue());
ret.put("result", true);
ret.put("value", rate);
} catch (Error err) {
Expand All @@ -225,10 +251,9 @@ public void remove(PluginCall call, String playerId) {
JSObject ret = new JSObject();
ret.put("method", "remove");
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
MediaPlayerState state = MediaPlayerStateProvider.getState(playerId);
state.mediaController.get().stop();
Fragment playerFragment = _currentActivity.getSupportFragmentManager().findFragmentByTag(playerId);
playerState.playerController.get().destroy();
MediaPlayerStateProvider.clearState(playerId);
if (playerFragment != null) {
_currentActivity.getSupportFragmentManager().beginTransaction().remove(playerFragment).commit();
}
Expand All @@ -248,10 +273,9 @@ public void removeAll(PluginCall call) {
_currentActivity.getSupportFragmentManager().beginTransaction().remove(fragment).commit();
try {
MediaPlayerState playerState = MediaPlayerStateProvider.getState(playerId);
playerState.playerController.get().destroy();
playerState.mediaController.get().stop();
} catch (Error ignored) {
}
MediaPlayerStateProvider.clearState(playerId);
MediaPlayerNotificationCenter.post(MediaPlayerNotification.create(playerId, MediaPlayerNotificationCenter.NOTIFICATION_TYPE.MEDIA_PLAYER_REMOVED).build());
});
JSObject ret = new JSObject();
Expand All @@ -270,14 +294,14 @@ private String getFinalPath(String url) {
return url;
} else if (url.startsWith("application") || url.contains("_capacitor_file_")) {
String filesDir = _currentActivity.getFilesDir() + "/";
path = filesDir + url.substring(url.lastIndexOf("files/") + 6);
path = filesDir + url.substring(url.lastIndexOf("files/") + "files/".length());
File file = new File(path);
if (!file.exists()) {
Log.e("Media Player", "File not found");
path = null;
}
} else if (url.contains("assets")) {
path = "file:///android_asset/" + url;
} else if (url.contains("public/assets")) {
path = "/android_asset/" + url;
} else if (url.startsWith("http")) {
path = url;
}
Expand Down
Loading

0 comments on commit f3e221d

Please sign in to comment.