use exoplayer as music backend
This commit is contained in:
parent
f3eedc0a0f
commit
5c7be50dfa
5 changed files with 204 additions and 157 deletions
|
|
@ -42,6 +42,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.6.1'
|
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.6.1'
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer:2.11.4'
|
||||||
|
|
||||||
implementation 'androidx.core:core:1.2.0'
|
implementation 'androidx.core:core:1.2.0'
|
||||||
implementation 'androidx.media:media:1.1.0'
|
implementation 'androidx.media:media:1.1.0'
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,163 @@
|
||||||
package com.dkanada.gramophone.service;
|
package com.dkanada.gramophone.service;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.dkanada.gramophone.R;
|
import com.dkanada.gramophone.R;
|
||||||
import com.dkanada.gramophone.service.playback.Playback;
|
import com.dkanada.gramophone.service.playback.Playback;
|
||||||
|
|
||||||
public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
public static final String TAG = MultiPlayer.class.getSimpleName();
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
|
import java.io.IOException;
|
||||||
private MediaPlayer mNextMediaPlayer;
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class MultiPlayer implements Playback {
|
||||||
|
public static final String TAG = MultiPlayer.class.getSimpleName();
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Nullable
|
private OkHttpClient httpClient;
|
||||||
|
private SimpleExoPlayer exoPlayer;
|
||||||
|
|
||||||
|
private ConcatenatingMediaSource mediaSource;
|
||||||
private Playback.PlaybackCallbacks callbacks;
|
private Playback.PlaybackCallbacks callbacks;
|
||||||
|
|
||||||
private boolean mIsInitialized = false;
|
private boolean isReady = false;
|
||||||
|
private boolean isPlaying = false;
|
||||||
|
private boolean isNew = false;
|
||||||
|
private boolean isFirst = true;
|
||||||
|
|
||||||
public MultiPlayer(final Context context) {
|
private ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
|
||||||
this.context = context;
|
@Override
|
||||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
|
Log.i(TAG,"onTracksChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setDataSource(@NonNull final String path) {
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
return true;
|
Log.i(TAG,"onLoadingChanged: isLoading = " + isLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||||
|
Log.i(TAG,"onPlayerStateChanged: playWhenReady = " + playWhenReady);
|
||||||
|
Log.i(TAG,"onPlayerStateChanged: playbackState = " + playbackState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int reason) {
|
||||||
|
Log.i(TAG,"onPositionDiscontinuity: reason = " + reason);
|
||||||
|
int windowIndex = exoPlayer.getCurrentWindowIndex();
|
||||||
|
|
||||||
|
if (windowIndex == 1) {
|
||||||
|
mediaSource.removeMediaSource(0);
|
||||||
|
if (mediaSource.getSize() != 0) {
|
||||||
|
// there are still songs left in the queue
|
||||||
|
callbacks.onTrackWentToNext();
|
||||||
|
} else {
|
||||||
|
callbacks.onTrackEnded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
|
Log.i(TAG,"onPlaybackError: " + error.getMessage());
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MultiPlayer(final Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
httpClient = new OkHttpClient();
|
||||||
|
exoPlayer = new SimpleExoPlayer.Builder(context).build();
|
||||||
|
mediaSource = new ConcatenatingMediaSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDataSource(@NonNull final String path) {
|
||||||
|
isReady = false;
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNew = true;
|
||||||
|
mediaSource = new ConcatenatingMediaSource();
|
||||||
|
|
||||||
|
exoPlayer.addListener(eventListener);
|
||||||
|
exoPlayer.prepare(mediaSource);
|
||||||
|
|
||||||
|
appendDataSource(path, true);
|
||||||
|
isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNextDataSource(@Nullable final String path) {
|
public void setNextDataSource(@Nullable final String path) {
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean appendDataSource(@NonNull final String path) {
|
if (mediaSource.getSize() >= 2) {
|
||||||
return true;
|
mediaSource.removeMediaSource(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendDataSource(path, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendDataSource(String path, boolean next) {
|
||||||
|
Uri uri = Uri.parse(path);
|
||||||
|
|
||||||
|
DataSource.Factory dataSource = new DefaultHttpDataSourceFactory(Util.getUserAgent(context, this.getClass().getName()));
|
||||||
|
httpClient.newCall(new Request.Builder().url(path).head().build()).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) {
|
||||||
|
MediaSource source;
|
||||||
|
if (response.header("Content-Type").equals("application/x-mpegURL")) {
|
||||||
|
source = new HlsMediaSource.Factory(dataSource).createMediaSource(uri);
|
||||||
|
} else {
|
||||||
|
source = new ProgressiveMediaSource.Factory(dataSource).createMediaSource(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaSource.addMediaSource(source);
|
||||||
|
if (!isFirst && next) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -48,135 +167,61 @@ public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, Media
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return mIsInitialized;
|
return isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean start() {
|
public boolean start() {
|
||||||
try {
|
isPlaying = true;
|
||||||
mCurrentMediaPlayer.start();
|
exoPlayer.setPlayWhenReady(true);
|
||||||
return true;
|
|
||||||
} catch (IllegalStateException e) {
|
if (isNew) {
|
||||||
return false;
|
callbacks.onTrackStarted();
|
||||||
|
isNew = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
mCurrentMediaPlayer.reset();
|
exoPlayer.release();
|
||||||
mIsInitialized = false;
|
isReady = false;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
stop();
|
|
||||||
|
|
||||||
mCurrentMediaPlayer.release();
|
|
||||||
if (mNextMediaPlayer != null) {
|
|
||||||
mNextMediaPlayer.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean pause() {
|
public boolean pause() {
|
||||||
try {
|
isPlaying = false;
|
||||||
mCurrentMediaPlayer.pause();
|
exoPlayer.setPlayWhenReady(false);
|
||||||
return true;
|
return true;
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
|
return isReady && isPlaying;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int duration() {
|
public int duration() {
|
||||||
if (!mIsInitialized) {
|
if (!isReady) return -1;
|
||||||
return -1;
|
return (int) exoPlayer.getDuration();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return mCurrentMediaPlayer.getDuration();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int position() {
|
public int position() {
|
||||||
if (!mIsInitialized) {
|
if (!isReady) return -1;
|
||||||
return -1;
|
return (int) exoPlayer.getCurrentPosition();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return mCurrentMediaPlayer.getCurrentPosition();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int seek(final int whereto) {
|
public int seek(int position) {
|
||||||
try {
|
exoPlayer.seekTo(position);
|
||||||
mCurrentMediaPlayer.seekTo(whereto);
|
return position;
|
||||||
return whereto;
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setVolume(final float vol) {
|
public boolean setVolume(float volume) {
|
||||||
try {
|
exoPlayer.setVolume(volume);
|
||||||
mCurrentMediaPlayer.setVolume(vol, vol);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean setAudioSessionId(final int sessionId) {
|
|
||||||
try {
|
|
||||||
mCurrentMediaPlayer.setAudioSessionId(sessionId);
|
|
||||||
return true;
|
|
||||||
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAudioSessionId() {
|
|
||||||
return mCurrentMediaPlayer.getAudioSessionId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
|
|
||||||
mIsInitialized = false;
|
|
||||||
mCurrentMediaPlayer.release();
|
|
||||||
mCurrentMediaPlayer = new MediaPlayer();
|
|
||||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
|
||||||
if (context != null) {
|
|
||||||
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompletion(final MediaPlayer mp) {
|
|
||||||
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
|
||||||
mIsInitialized = false;
|
|
||||||
mCurrentMediaPlayer.release();
|
|
||||||
mCurrentMediaPlayer = mNextMediaPlayer;
|
|
||||||
mIsInitialized = true;
|
|
||||||
mNextMediaPlayer = null;
|
|
||||||
if (callbacks != null) callbacks.onTrackWentToNext();
|
|
||||||
} else {
|
|
||||||
if (callbacks != null) callbacks.onTrackEnded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
|
|
||||||
position = restoredPosition;
|
position = restoredPosition;
|
||||||
openCurrent();
|
openCurrent();
|
||||||
prepareNext();
|
|
||||||
|
|
||||||
if (restoredPositionInTrack > 0) seek(restoredPositionInTrack);
|
if (restoredPositionInTrack > 0) seek(restoredPositionInTrack);
|
||||||
|
|
||||||
|
|
@ -451,7 +450,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
queueSaveHandler.removeCallbacksAndMessages(null);
|
queueSaveHandler.removeCallbacksAndMessages(null);
|
||||||
queueSaveHandlerThread.quitSafely();
|
queueSaveHandlerThread.quitSafely();
|
||||||
|
|
||||||
playback.release();
|
playback.stop();
|
||||||
playback = null;
|
playback = null;
|
||||||
mediaSession.release();
|
mediaSession.release();
|
||||||
}
|
}
|
||||||
|
|
@ -468,24 +467,20 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playSongAt(getNextPosition(force));
|
playSongAt(getNextPosition(force));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openTrackAndPrepareNextAt(int position) {
|
private void openTrackAndPrepareNextAt(int position) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
boolean prepared = openCurrent();
|
|
||||||
if (prepared) prepareNextImpl();
|
openCurrent();
|
||||||
|
|
||||||
notifyChange(META_CHANGED);
|
notifyChange(META_CHANGED);
|
||||||
notHandledMetaChangedForCurrentTrack = false;
|
notHandledMetaChangedForCurrentTrack = false;
|
||||||
return prepared;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openCurrent() {
|
private void openCurrent() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
try {
|
playback.setDataSource(getTrackUri(getCurrentSong()));
|
||||||
return playback.setDataSource(getTrackUri(getCurrentSong()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,16 +489,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
|
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean prepareNextImpl() {
|
private void prepareNextImpl() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
try {
|
nextPosition = getNextPosition(false);
|
||||||
int nextPosition = getNextPosition(false);
|
|
||||||
playback.setNextDataSource(getTrackUri(getSongAt(nextPosition)));
|
playback.setNextDataSource(getTrackUri(getSongAt(nextPosition)));
|
||||||
this.nextPosition = nextPosition;
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -805,11 +794,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSongAtImpl(int position) {
|
private void playSongAtImpl(int position) {
|
||||||
if (openTrackAndPrepareNextAt(position)) {
|
openTrackAndPrepareNextAt(position);
|
||||||
play();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause() {
|
public void pause() {
|
||||||
|
|
@ -1072,10 +1057,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAudioSessionId() {
|
|
||||||
return playback.getAudioSessionId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaSessionCompat getMediaSession() {
|
public MediaSessionCompat getMediaSession() {
|
||||||
return mediaSession;
|
return mediaSession;
|
||||||
}
|
}
|
||||||
|
|
@ -1093,13 +1074,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case PreferenceUtil.GAPLESS_PLAYBACK:
|
|
||||||
if (sharedPreferences.getBoolean(key, false)) {
|
|
||||||
prepareNext();
|
|
||||||
} else {
|
|
||||||
playback.setNextDataSource(null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PreferenceUtil.SHOW_ALBUM_COVER:
|
case PreferenceUtil.SHOW_ALBUM_COVER:
|
||||||
case PreferenceUtil.BLUR_ALBUM_COVER:
|
case PreferenceUtil.BLUR_ALBUM_COVER:
|
||||||
updateMediaSessionMetaData();
|
updateMediaSessionMetaData();
|
||||||
|
|
@ -1114,6 +1088,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTrackStarted() {
|
||||||
|
handleAndSendChangeInternal(PLAY_STATE_CHANGED);
|
||||||
|
prepareNext();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackWentToNext() {
|
public void onTrackWentToNext() {
|
||||||
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
|
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.dkanada.gramophone.service.playback;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public interface Playback {
|
public interface Playback {
|
||||||
boolean setDataSource(String path);
|
void setDataSource(String path);
|
||||||
|
|
||||||
void setNextDataSource(@Nullable String path);
|
void setNextDataSource(@Nullable String path);
|
||||||
|
|
||||||
|
|
@ -15,8 +15,6 @@ public interface Playback {
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void release();
|
|
||||||
|
|
||||||
boolean pause();
|
boolean pause();
|
||||||
|
|
||||||
boolean isPlaying();
|
boolean isPlaying();
|
||||||
|
|
@ -29,11 +27,9 @@ public interface Playback {
|
||||||
|
|
||||||
boolean setVolume(float vol);
|
boolean setVolume(float vol);
|
||||||
|
|
||||||
boolean setAudioSessionId(int sessionId);
|
|
||||||
|
|
||||||
int getAudioSessionId();
|
|
||||||
|
|
||||||
interface PlaybackCallbacks {
|
interface PlaybackCallbacks {
|
||||||
|
void onTrackStarted();
|
||||||
|
|
||||||
void onTrackWentToNext();
|
void onTrackWentToNext();
|
||||||
|
|
||||||
void onTrackEnded();
|
void onTrackEnded();
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.dkanada.gramophone.App;
|
import com.dkanada.gramophone.App;
|
||||||
|
|
@ -16,6 +17,7 @@ import com.dkanada.gramophone.model.Artist;
|
||||||
import com.dkanada.gramophone.model.Genre;
|
import com.dkanada.gramophone.model.Genre;
|
||||||
import com.dkanada.gramophone.model.Song;
|
import com.dkanada.gramophone.model.Song;
|
||||||
|
|
||||||
|
import org.jellyfin.apiclient.interaction.ApiClient;
|
||||||
import org.jellyfin.apiclient.interaction.Response;
|
import org.jellyfin.apiclient.interaction.Response;
|
||||||
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
|
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
|
||||||
|
|
||||||
|
|
@ -24,7 +26,30 @@ import java.util.Locale;
|
||||||
|
|
||||||
public class MusicUtil {
|
public class MusicUtil {
|
||||||
public static Uri getSongFileUri(Song song) {
|
public static Uri getSongFileUri(Song song) {
|
||||||
return Uri.parse(App.getApiClient().getApiUrl() + "/Audio/" + song.id + "/stream?static=true");
|
if (song.id == null) return null;
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
ApiClient apiClient = App.getApiClient();
|
||||||
|
|
||||||
|
builder.append(apiClient.getApiUrl());
|
||||||
|
builder.append("/Audio/");
|
||||||
|
builder.append(song.id);
|
||||||
|
builder.append("/universal");
|
||||||
|
builder.append("?UserId=" + apiClient.getCurrentUserId());
|
||||||
|
builder.append("&DeviceId=" + apiClient.getDeviceId());
|
||||||
|
|
||||||
|
// web max is 12444445 and 320kbps is 320000
|
||||||
|
builder.append("&MaxStreamingBitrate=10000000");
|
||||||
|
builder.append("&Container=flac");
|
||||||
|
builder.append("&TranscodingContainer=ts");
|
||||||
|
builder.append("&TranscodingProtocol=hls");
|
||||||
|
|
||||||
|
// preferred codec when transcoding
|
||||||
|
builder.append("&AudioCodec=aac");
|
||||||
|
builder.append("&api_key=" + apiClient.getAccessToken());
|
||||||
|
|
||||||
|
Log.i(MusicUtil.class.getName(), "playing audio: " + builder);
|
||||||
|
return Uri.parse(builder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue