mirror of
https://github.com/clangen/musikcube.git
synced 2025-04-18 11:42:44 +00:00
Converted PlayerWrapper, MediaPlayerWrapper, ExoPlayerWrapper to Kotlin
to mess around. The rest of the project will probably follow over the next few weeks.
This commit is contained in:
parent
1e93ab2162
commit
a60f33fa19
@ -1,366 +0,0 @@
|
|||||||
package io.casey.musikcube.remote.playback;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import com.danikula.videocache.CacheListener;
|
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
|
||||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
|
||||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
|
||||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import io.casey.musikcube.remote.Application;
|
|
||||||
import io.casey.musikcube.remote.util.NetworkUtil;
|
|
||||||
import io.casey.musikcube.remote.util.Preconditions;
|
|
||||||
import io.casey.musikcube.remote.websocket.Prefs;
|
|
||||||
import okhttp3.Cache;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
|
|
||||||
public class ExoPlayerWrapper extends PlayerWrapper {
|
|
||||||
private static OkHttpClient audioStreamHttpClient = null;
|
|
||||||
|
|
||||||
private final SharedPreferences prefs;
|
|
||||||
private DataSource.Factory datasources;
|
|
||||||
private ExtractorsFactory extractors;
|
|
||||||
private MediaSource source;
|
|
||||||
private SimpleExoPlayer player;
|
|
||||||
private boolean prefetch;
|
|
||||||
private Context context;
|
|
||||||
private long lastPosition = -1;
|
|
||||||
private int percentAvailable = 0;
|
|
||||||
private String originalUri, resolvedUri;
|
|
||||||
private boolean transcoding;
|
|
||||||
|
|
||||||
private void initHttpClient(final String uri) {
|
|
||||||
if (StreamProxy.ENABLED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (ExoPlayerWrapper.class) {
|
|
||||||
if (audioStreamHttpClient == null) {
|
|
||||||
final File path = new File(context.getExternalCacheDir(), "audio");
|
|
||||||
|
|
||||||
int diskCacheIndex = prefs.getInt(
|
|
||||||
Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX);
|
|
||||||
|
|
||||||
if (diskCacheIndex < 0 || diskCacheIndex > StreamProxy.CACHE_SETTING_TO_BYTES.size()) {
|
|
||||||
diskCacheIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
|
||||||
.cache(new Cache(path, StreamProxy.CACHE_SETTING_TO_BYTES.get(diskCacheIndex)))
|
|
||||||
.addInterceptor((chain) -> {
|
|
||||||
Request request = chain.request();
|
|
||||||
final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD);
|
|
||||||
final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP);
|
|
||||||
request = request.newBuilder().addHeader("Authorization", "Basic " + encoded).build();
|
|
||||||
return chain.proceed(request);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
|
|
||||||
NetworkUtil.disableCertificateValidation(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
audioStreamHttpClient = builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.startsWith("http")) {
|
|
||||||
this.datasources = new OkHttpDataSourceFactory(
|
|
||||||
audioStreamHttpClient,
|
|
||||||
Util.getUserAgent(context, "musikdroid"),
|
|
||||||
new DefaultBandwidthMeter());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.datasources = new DefaultDataSourceFactory(
|
|
||||||
context, Util.getUserAgent(context, "musikdroid"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExoPlayerWrapper() {
|
|
||||||
this.context = Application.getInstance();
|
|
||||||
final DefaultBandwidthMeter bandwidth = new DefaultBandwidthMeter();
|
|
||||||
final TrackSelection.Factory trackFactory = new AdaptiveTrackSelection.Factory(bandwidth);
|
|
||||||
final TrackSelector trackSelector = new DefaultTrackSelector(trackFactory);
|
|
||||||
this.player = ExoPlayerFactory.newSimpleInstance(this.context, trackSelector);
|
|
||||||
this.extractors = new DefaultExtractorsFactory();
|
|
||||||
this.player.addListener(eventListener);
|
|
||||||
this.datasources = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "musikdroid"));
|
|
||||||
this.prefs = Application.getInstance().getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
|
||||||
this.transcoding = this.prefs.getInt(Prefs.Key.TRANSCODER_BITRATE_INDEX, 0) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void play(String uri) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
initHttpClient(uri);
|
|
||||||
this.originalUri = uri;
|
|
||||||
this.resolvedUri = StreamProxy.getProxyUrl(context, uri);
|
|
||||||
addCacheListener();
|
|
||||||
this.source = new ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null);
|
|
||||||
this.player.setPlayWhenReady(true);
|
|
||||||
this.player.prepare(this.source);
|
|
||||||
addActivePlayer(this);
|
|
||||||
setState(State.Preparing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void prefetch(String uri) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
initHttpClient(uri);
|
|
||||||
this.originalUri = uri;
|
|
||||||
this.prefetch = true;
|
|
||||||
this.resolvedUri = StreamProxy.getProxyUrl(context, uri);
|
|
||||||
addCacheListener();
|
|
||||||
this.source = new ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null);
|
|
||||||
this.player.setPlayWhenReady(false);
|
|
||||||
this.player.prepare(this.source);
|
|
||||||
addActivePlayer(this);
|
|
||||||
setState(State.Preparing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pause() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
this.prefetch = true;
|
|
||||||
|
|
||||||
if (this.getState() == State.Playing) {
|
|
||||||
this.player.setPlayWhenReady(false);
|
|
||||||
setState(State.Paused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resume() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
switch (this.getState()) {
|
|
||||||
case Paused:
|
|
||||||
case Prepared:
|
|
||||||
this.player.setPlayWhenReady(true);
|
|
||||||
setState(State.Playing);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error:
|
|
||||||
this.player.setPlayWhenReady(this.lastPosition == -1);
|
|
||||||
this.player.prepare(this.source);
|
|
||||||
setState(State.Preparing);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prefetch = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPosition(int millis) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
this.lastPosition = -1;
|
|
||||||
if (this.player.getPlaybackState() != ExoPlayer.STATE_IDLE) {
|
|
||||||
if (this.player.isCurrentWindowSeekable()) {
|
|
||||||
long offset = millis;
|
|
||||||
|
|
||||||
/* if we're transcoding we don't want to seek arbitrarily because it may put
|
|
||||||
a lot of pressure on the backend. just allow seeking up to what we currently
|
|
||||||
have buffered! */
|
|
||||||
if (transcoding && percentAvailable != 100) {
|
|
||||||
/* give ourselves 2% wiggle room! */
|
|
||||||
float percent = (float) Math.max(0, percentAvailable - 2) / 100.0f;
|
|
||||||
long totalMs = this.player.getDuration();
|
|
||||||
long available = (long) ((float) totalMs * percent);
|
|
||||||
offset = Math.min(millis, available);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.seekTo(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPosition() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
return (int) this.player.getCurrentPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDuration() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
return (int) this.player.getDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateVolume() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
this.player.setVolume(getGlobalVolume());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNextMediaPlayer(PlayerWrapper wrapper) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBufferedPercent() {
|
|
||||||
return transcoding ? percentAvailable : 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
setState(State.Killing);
|
|
||||||
removeActivePlayer(this);
|
|
||||||
removeCacheListener();
|
|
||||||
if (this.player != null) {
|
|
||||||
this.player.setPlayWhenReady(false);
|
|
||||||
this.player.removeListener(eventListener);
|
|
||||||
this.player.stop();
|
|
||||||
this.player.release();
|
|
||||||
}
|
|
||||||
setState(State.Disposed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnStateChangedListener(OnStateChangedListener listener) {
|
|
||||||
super.setOnStateChangedListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean dead() {
|
|
||||||
final State state = getState();
|
|
||||||
return (state == State.Killing || state == State.Disposed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addCacheListener() {
|
|
||||||
if (StreamProxy.ENABLED) {
|
|
||||||
if (StreamProxy.isCached(this.originalUri)) {
|
|
||||||
percentAvailable = 100;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
StreamProxy.registerCacheListener(this.cacheListener, this.originalUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
percentAvailable = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeCacheListener() {
|
|
||||||
if (StreamProxy.ENABLED) {
|
|
||||||
StreamProxy.unregisterCacheListener(this.cacheListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CacheListener cacheListener = (file, uri, percent) -> {
|
|
||||||
//Log.e("CLCLCL", String.format("%d", percent));
|
|
||||||
percentAvailable = percent;
|
|
||||||
};
|
|
||||||
|
|
||||||
private ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
|
|
||||||
@Override
|
|
||||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingChanged(boolean isLoading) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (playbackState == ExoPlayer.STATE_BUFFERING) {
|
|
||||||
setState(State.Buffering);
|
|
||||||
}
|
|
||||||
else if (playbackState == ExoPlayer.STATE_READY) {
|
|
||||||
if (dead()) {
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(State.Prepared);
|
|
||||||
|
|
||||||
player.setVolume(getGlobalVolume());
|
|
||||||
|
|
||||||
if (lastPosition != -1) {
|
|
||||||
player.seekTo(lastPosition);
|
|
||||||
lastPosition = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prefetch) {
|
|
||||||
player.setPlayWhenReady(true);
|
|
||||||
setState(State.Playing);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(State.Paused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (playbackState == ExoPlayer.STATE_ENDED) {
|
|
||||||
setState(State.Finished);
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
lastPosition = player.getCurrentPosition();
|
|
||||||
|
|
||||||
switch (getState()) {
|
|
||||||
case Preparing:
|
|
||||||
case Prepared:
|
|
||||||
case Playing:
|
|
||||||
case Paused:
|
|
||||||
setState(State.Error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPositionDiscontinuity() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -0,0 +1,335 @@
|
|||||||
|
package io.casey.musikcube.remote.playback
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Base64
|
||||||
|
import com.google.android.exoplayer2.*
|
||||||
|
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory
|
||||||
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory
|
||||||
|
import com.google.android.exoplayer2.source.ExtractorMediaSource
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray
|
||||||
|
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
|
||||||
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
|
import com.google.android.exoplayer2.util.Util
|
||||||
|
import io.casey.musikcube.remote.Application
|
||||||
|
import io.casey.musikcube.remote.util.NetworkUtil
|
||||||
|
import io.casey.musikcube.remote.util.Preconditions
|
||||||
|
import io.casey.musikcube.remote.websocket.Prefs
|
||||||
|
import io.casey.musikcube.remote.playback.StreamProxy.*
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class ExoPlayerWrapper : PlayerWrapper() {
|
||||||
|
private val prefs: SharedPreferences
|
||||||
|
private var datasources: DataSource.Factory? = null
|
||||||
|
private val extractors: ExtractorsFactory
|
||||||
|
private var source: MediaSource? = null
|
||||||
|
private val player: SimpleExoPlayer?
|
||||||
|
private var prefetch: Boolean = false
|
||||||
|
private val context: Context
|
||||||
|
private var lastPosition: Long = -1
|
||||||
|
private var percentAvailable = 0
|
||||||
|
private var originalUri: String? = null
|
||||||
|
private var resolvedUri: String? = null
|
||||||
|
private val transcoding: Boolean
|
||||||
|
|
||||||
|
private fun initHttpClient(uri: String) {
|
||||||
|
if (StreamProxy.ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(ExoPlayerWrapper::class.java) {
|
||||||
|
if (audioStreamHttpClient == null) {
|
||||||
|
val path = File(context.externalCacheDir, "audio")
|
||||||
|
|
||||||
|
var diskCacheIndex = prefs.getInt(
|
||||||
|
Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX)
|
||||||
|
|
||||||
|
if (diskCacheIndex < 0 || diskCacheIndex > StreamProxy.CACHE_SETTING_TO_BYTES.size) {
|
||||||
|
diskCacheIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = OkHttpClient.Builder()
|
||||||
|
.cache(Cache(path, CACHE_SETTING_TO_BYTES[diskCacheIndex] ?: MINIMUM_CACHE_SIZE_BYTES))
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
var request = chain.request()
|
||||||
|
val userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD)!!
|
||||||
|
val encoded = Base64.encodeToString(userPass.toByteArray(), Base64.NO_WRAP)
|
||||||
|
request = request.newBuilder().addHeader("Authorization", "Basic " + encoded).build()
|
||||||
|
chain.proceed(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
|
||||||
|
NetworkUtil.disableCertificateValidation(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
audioStreamHttpClient = builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.startsWith("http")) {
|
||||||
|
this.datasources = OkHttpDataSourceFactory(
|
||||||
|
audioStreamHttpClient,
|
||||||
|
Util.getUserAgent(context, "musikdroid"),
|
||||||
|
DefaultBandwidthMeter())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.datasources = DefaultDataSourceFactory(
|
||||||
|
context, Util.getUserAgent(context, "musikdroid"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.context = Application.getInstance()
|
||||||
|
val bandwidth = DefaultBandwidthMeter()
|
||||||
|
val trackFactory = AdaptiveTrackSelection.Factory(bandwidth)
|
||||||
|
val trackSelector = DefaultTrackSelector(trackFactory)
|
||||||
|
this.player = ExoPlayerFactory.newSimpleInstance(this.context, trackSelector)
|
||||||
|
this.extractors = DefaultExtractorsFactory()
|
||||||
|
this.datasources = DefaultDataSourceFactory(context, Util.getUserAgent(context, "musikdroid"))
|
||||||
|
this.prefs = Application.getInstance().getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
|
||||||
|
this.transcoding = this.prefs.getInt(Prefs.Key.TRANSCODER_BITRATE_INDEX, 0) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(uri: String) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (!dead()) {
|
||||||
|
initHttpClient(uri)
|
||||||
|
this.originalUri = uri
|
||||||
|
this.resolvedUri = StreamProxy.getProxyUrl(context, uri)
|
||||||
|
addCacheListener()
|
||||||
|
this.source = ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null)
|
||||||
|
this.player!!.playWhenReady = true
|
||||||
|
this.player.prepare(this.source)
|
||||||
|
PlayerWrapper.addActivePlayer(this)
|
||||||
|
state = State.Preparing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prefetch(uri: String) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (!dead()) {
|
||||||
|
initHttpClient(uri)
|
||||||
|
this.originalUri = uri
|
||||||
|
this.prefetch = true
|
||||||
|
this.resolvedUri = StreamProxy.getProxyUrl(context, uri)
|
||||||
|
addCacheListener()
|
||||||
|
this.source = ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null)
|
||||||
|
this.player!!.playWhenReady = false
|
||||||
|
this.player.prepare(this.source)
|
||||||
|
PlayerWrapper.addActivePlayer(this)
|
||||||
|
state = State.Preparing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
this.prefetch = true
|
||||||
|
|
||||||
|
if (this.state == State.Playing) {
|
||||||
|
this.player!!.playWhenReady = false
|
||||||
|
state = State.Paused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resume() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
when (this.state) {
|
||||||
|
State.Paused, State.Prepared -> {
|
||||||
|
this.player!!.playWhenReady = true
|
||||||
|
state = State.Playing
|
||||||
|
}
|
||||||
|
|
||||||
|
State.Error -> {
|
||||||
|
this.player!!.playWhenReady = this.lastPosition == -1L
|
||||||
|
this.player.prepare(this.source)
|
||||||
|
state = State.Preparing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetch = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override var position: Int
|
||||||
|
get(): Int {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
return this.player!!.currentPosition.toInt()
|
||||||
|
}
|
||||||
|
set(millis) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
this.lastPosition = -1
|
||||||
|
if (this.player!!.playbackState != ExoPlayer.STATE_IDLE) {
|
||||||
|
if (this.player.isCurrentWindowSeekable) {
|
||||||
|
var offset = millis.toLong()
|
||||||
|
|
||||||
|
/* if we're transcoding we don't want to seek arbitrarily because it may put
|
||||||
|
a lot of pressure on the backend. just allow seeking up to what we currently
|
||||||
|
have buffered! */
|
||||||
|
if (transcoding && percentAvailable != 100) {
|
||||||
|
/* give ourselves 2% wiggle room! */
|
||||||
|
val percent = Math.max(0, percentAvailable - 2).toFloat() / 100.0f
|
||||||
|
val totalMs = this.player.duration
|
||||||
|
val available = (totalMs.toFloat() * percent).toLong()
|
||||||
|
offset = Math.min(millis.toLong(), available)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.seekTo(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val duration: Int
|
||||||
|
get() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
return this.player!!.duration.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val bufferedPercent: Int
|
||||||
|
get() {
|
||||||
|
return if (transcoding) percentAvailable else 100
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateVolume() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
this.player!!.volume = PlayerWrapper.getVolume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNextMediaPlayer(wrapper: PlayerWrapper?) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (!dead()) {
|
||||||
|
state = State.Killing
|
||||||
|
PlayerWrapper.removeActivePlayer(this)
|
||||||
|
removeCacheListener()
|
||||||
|
this.player?.playWhenReady = false
|
||||||
|
this.player?.removeListener(eventListener)
|
||||||
|
this.player?.stop()
|
||||||
|
this.player?.release()
|
||||||
|
state = State.Disposed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOnStateChangedListener(listener: PlayerWrapper.OnStateChangedListener?) {
|
||||||
|
super.setOnStateChangedListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dead(): Boolean {
|
||||||
|
val state = state
|
||||||
|
return state == State.Killing || state == State.Disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCacheListener() {
|
||||||
|
if (StreamProxy.ENABLED) {
|
||||||
|
if (StreamProxy.isCached(this.originalUri)) {
|
||||||
|
percentAvailable = 100
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
StreamProxy.registerCacheListener(this.cacheListener, this.originalUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
percentAvailable = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeCacheListener() {
|
||||||
|
if (StreamProxy.ENABLED) {
|
||||||
|
StreamProxy.unregisterCacheListener(this.cacheListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cacheListener = { _: File, _: String, percent: Int ->
|
||||||
|
//Log.e("CLCLCL", String.format("%d", percent));
|
||||||
|
percentAvailable = percent
|
||||||
|
}
|
||||||
|
|
||||||
|
private var eventListener = object : ExoPlayer.EventListener {
|
||||||
|
override fun onTimelineChanged(timeline: Timeline, manifest: Any?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadingChanged(isLoading: Boolean) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (playbackState == ExoPlayer.STATE_BUFFERING) {
|
||||||
|
state = State.Buffering
|
||||||
|
}
|
||||||
|
else if (playbackState == ExoPlayer.STATE_READY) {
|
||||||
|
if (dead()) {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = State.Prepared
|
||||||
|
|
||||||
|
player!!.volume = PlayerWrapper.getVolume()
|
||||||
|
|
||||||
|
if (lastPosition != -1L) {
|
||||||
|
player.seekTo(lastPosition)
|
||||||
|
lastPosition = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prefetch) {
|
||||||
|
player.playWhenReady = true
|
||||||
|
state = State.Playing
|
||||||
|
} else {
|
||||||
|
state = State.Paused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (playbackState == ExoPlayer.STATE_ENDED) {
|
||||||
|
state = State.Finished
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayerError(error: ExoPlaybackException) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
lastPosition = player!!.currentPosition
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
State.Preparing,
|
||||||
|
State.Prepared,
|
||||||
|
State.Playing,
|
||||||
|
State.Paused ->
|
||||||
|
state = State.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionDiscontinuity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var audioStreamHttpClient: OkHttpClient? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.player!!.addListener(eventListener)
|
||||||
|
}
|
||||||
|
}
|
@ -1,242 +0,0 @@
|
|||||||
package io.casey.musikcube.remote.playback;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import io.casey.musikcube.remote.Application;
|
|
||||||
import io.casey.musikcube.remote.util.Preconditions;
|
|
||||||
import io.casey.musikcube.remote.websocket.Prefs;
|
|
||||||
|
|
||||||
public class MediaPlayerWrapper extends PlayerWrapper {
|
|
||||||
private static final String TAG = "MediaPlayerWrapper";
|
|
||||||
|
|
||||||
private MediaPlayer player = new MediaPlayer();
|
|
||||||
private int seekTo;
|
|
||||||
private boolean prefetching;
|
|
||||||
private Context context = Application.getInstance();
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private int bufferedPercent;
|
|
||||||
|
|
||||||
public MediaPlayerWrapper() {
|
|
||||||
this.prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void play(final String uri) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
try {
|
|
||||||
setState(State.Preparing);
|
|
||||||
|
|
||||||
final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD);
|
|
||||||
final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP);
|
|
||||||
final Map<String, String> headers = new HashMap<>();
|
|
||||||
headers.put("Authorization", "Basic " + encoded);
|
|
||||||
|
|
||||||
player.setDataSource(
|
|
||||||
context,
|
|
||||||
Uri.parse(StreamProxy.getProxyUrl(context, uri)),
|
|
||||||
headers);
|
|
||||||
|
|
||||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
|
||||||
player.setOnPreparedListener(onPrepared);
|
|
||||||
player.setOnErrorListener(onError);
|
|
||||||
player.setOnCompletionListener(onCompleted);
|
|
||||||
player.setOnBufferingUpdateListener(onBuffering);
|
|
||||||
player.setWakeMode(Application.getInstance(), PowerManager.PARTIAL_WAKE_LOCK);
|
|
||||||
player.prepareAsync();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Log.e(TAG, "setDataSource failed: " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void prefetch(final String uri) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
this.prefetching = true;
|
|
||||||
play(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pause() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying()) {
|
|
||||||
player.pause();
|
|
||||||
setState(State.Paused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPosition(int millis) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying()) {
|
|
||||||
this.player.seekTo(millis);
|
|
||||||
this.seekTo = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.seekTo = millis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPosition() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying()) {
|
|
||||||
return this.player.getCurrentPosition();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDuration() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying()) {
|
|
||||||
return this.player.getDuration();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resume() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
final State state = getState();
|
|
||||||
if (state == State.Prepared || state == State.Paused) {
|
|
||||||
player.start();
|
|
||||||
setState(State.Playing);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
prefetching = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNextMediaPlayer(final PlayerWrapper wrapper) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying()) {
|
|
||||||
try {
|
|
||||||
this.player.setNextMediaPlayer(wrapper != null ? ((MediaPlayerWrapper) wrapper).player : null);
|
|
||||||
}
|
|
||||||
catch (IllegalStateException ex) {
|
|
||||||
Log.d(TAG, "invalid state for setNextMediaPlayer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateVolume() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
final State state = getState();
|
|
||||||
if (state != State.Preparing && state != State.Disposed) {
|
|
||||||
final float volume = getGlobalVolume();
|
|
||||||
player.setVolume(volume, volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBufferedPercent() {
|
|
||||||
return bufferedPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPreparedOrPlaying() {
|
|
||||||
final State state = getState();
|
|
||||||
return state == State.Playing || state == State.Prepared;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
removeActivePlayer(this);
|
|
||||||
|
|
||||||
if (getState() != State.Preparing) {
|
|
||||||
try {
|
|
||||||
this.player.setNextMediaPlayer(null);
|
|
||||||
}
|
|
||||||
catch (IllegalStateException ex) {
|
|
||||||
Log.d(TAG, "failed to setNextMediaPlayer(null)");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.player.stop();
|
|
||||||
}
|
|
||||||
catch (IllegalStateException ex) {
|
|
||||||
Log.d(TAG, "failed to stop()");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.player.reset();
|
|
||||||
}
|
|
||||||
catch (IllegalStateException ex) {
|
|
||||||
Log.d(TAG, "failed to reset()");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.release();
|
|
||||||
|
|
||||||
setOnStateChangedListener(null);
|
|
||||||
setState(State.Disposed);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setState(State.Killing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaPlayer.OnPreparedListener onPrepared = (mediaPlayer) -> {
|
|
||||||
if (this.getState() == State.Killing) {
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final float volume = getGlobalVolume();
|
|
||||||
player.setVolume(volume, volume);
|
|
||||||
|
|
||||||
addActivePlayer(this);
|
|
||||||
|
|
||||||
if (prefetching) {
|
|
||||||
setState(State.Prepared);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.player.start();
|
|
||||||
|
|
||||||
if (this.seekTo != 0) {
|
|
||||||
setPosition(this.seekTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(State.Playing);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prefetching = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private MediaPlayer.OnErrorListener onError = (player, what, extra) -> {
|
|
||||||
setState(State.Error);
|
|
||||||
dispose();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private MediaPlayer.OnCompletionListener onCompleted = (mp) -> {
|
|
||||||
setState(State.Finished);
|
|
||||||
dispose();
|
|
||||||
};
|
|
||||||
|
|
||||||
private MediaPlayer.OnBufferingUpdateListener onBuffering = (mp, percent) -> {
|
|
||||||
bufferedPercent = percent;
|
|
||||||
};
|
|
||||||
}
|
|
@ -0,0 +1,232 @@
|
|||||||
|
package io.casey.musikcube.remote.playback
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
import io.casey.musikcube.remote.Application
|
||||||
|
import io.casey.musikcube.remote.util.Preconditions
|
||||||
|
import io.casey.musikcube.remote.websocket.Prefs
|
||||||
|
|
||||||
|
class MediaPlayerWrapper : PlayerWrapper() {
|
||||||
|
|
||||||
|
private val player = MediaPlayer()
|
||||||
|
private var seekTo: Int = 0
|
||||||
|
private var prefetching: Boolean = false
|
||||||
|
private val context = Application.getInstance()
|
||||||
|
private val prefs: SharedPreferences
|
||||||
|
override var bufferedPercent: Int = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(uri: String) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
try {
|
||||||
|
state = State.Preparing
|
||||||
|
|
||||||
|
val userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD)!!
|
||||||
|
val encoded = Base64.encodeToString(userPass.toByteArray(), Base64.NO_WRAP)
|
||||||
|
val headers = HashMap<String, String>()
|
||||||
|
headers.put("Authorization", "Basic " + encoded)
|
||||||
|
|
||||||
|
player.setDataSource(
|
||||||
|
context,
|
||||||
|
Uri.parse(StreamProxy.getProxyUrl(context, uri)),
|
||||||
|
headers)
|
||||||
|
|
||||||
|
player.setAudioStreamType(AudioManager.STREAM_MUSIC)
|
||||||
|
player.setOnPreparedListener(onPrepared)
|
||||||
|
player.setOnErrorListener(onError)
|
||||||
|
player.setOnCompletionListener(onCompleted)
|
||||||
|
player.setOnBufferingUpdateListener(onBuffering)
|
||||||
|
player.setWakeMode(Application.getInstance(), PowerManager.PARTIAL_WAKE_LOCK)
|
||||||
|
player.prepareAsync()
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
Log.e(TAG, "setDataSource failed: " + e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prefetch(uri: String) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
this.prefetching = true
|
||||||
|
play(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (isPreparedOrPlaying) {
|
||||||
|
player.pause()
|
||||||
|
state = State.Paused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var position: Int
|
||||||
|
get() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (isPreparedOrPlaying) {
|
||||||
|
return this.player.currentPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
set(millis) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (isPreparedOrPlaying) {
|
||||||
|
this.player.seekTo(millis)
|
||||||
|
this.seekTo = 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.seekTo = millis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val duration: Int
|
||||||
|
get() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (isPreparedOrPlaying) {
|
||||||
|
return this.player.duration
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resume() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (state === State.Prepared || state === State.Paused) {
|
||||||
|
player.start()
|
||||||
|
this.state = State.Playing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prefetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNextMediaPlayer(wrapper: PlayerWrapper?) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (isPreparedOrPlaying) {
|
||||||
|
try {
|
||||||
|
if (wrapper is MediaPlayerWrapper) {
|
||||||
|
this.player.setNextMediaPlayer(wrapper.player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex: IllegalStateException) {
|
||||||
|
Log.d(TAG, "invalid state for setNextMediaPlayer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateVolume() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
val state = state
|
||||||
|
if (state !== State.Preparing && state !== State.Disposed) {
|
||||||
|
val volume = PlayerWrapper.getVolume()
|
||||||
|
player.setVolume(volume, volume)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isPreparedOrPlaying: Boolean
|
||||||
|
get() {
|
||||||
|
return state === State.Playing || state === State.Prepared
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
PlayerWrapper.removeActivePlayer(this)
|
||||||
|
|
||||||
|
if (state !== State.Preparing) {
|
||||||
|
try {
|
||||||
|
this.player.setNextMediaPlayer(null)
|
||||||
|
}
|
||||||
|
catch (ex: IllegalStateException) {
|
||||||
|
Log.d(TAG, "failed to setNextMediaPlayer(null)")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.player.stop()
|
||||||
|
}
|
||||||
|
catch (ex: IllegalStateException) {
|
||||||
|
Log.d(TAG, "failed to stop()")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.player.reset()
|
||||||
|
}
|
||||||
|
catch (ex: IllegalStateException) {
|
||||||
|
Log.d(TAG, "failed to reset()")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.release()
|
||||||
|
|
||||||
|
setOnStateChangedListener(null)
|
||||||
|
state = State.Disposed
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = State.Killing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onPrepared = { mediaPlayer: MediaPlayer ->
|
||||||
|
if (this.state === State.Killing) {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val volume = PlayerWrapper.getVolume()
|
||||||
|
player.setVolume(volume, volume)
|
||||||
|
|
||||||
|
PlayerWrapper.addActivePlayer(this)
|
||||||
|
|
||||||
|
if (prefetching) {
|
||||||
|
state = State.Prepared
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.player.start()
|
||||||
|
|
||||||
|
if (this.seekTo != 0) {
|
||||||
|
position = this.seekTo
|
||||||
|
}
|
||||||
|
|
||||||
|
state = State.Playing
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onError = { _: MediaPlayer, _: Int, _: Int ->
|
||||||
|
state = State.Error
|
||||||
|
dispose()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onCompleted = { _: MediaPlayer ->
|
||||||
|
state = State.Finished
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onBuffering = { _: MediaPlayer, percent: Int -> bufferedPercent = percent }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "MediaPlayerWrapper"
|
||||||
|
}
|
||||||
|
}
|
@ -1,149 +0,0 @@
|
|||||||
package io.casey.musikcube.remote.playback;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import io.casey.musikcube.remote.util.Preconditions;
|
|
||||||
|
|
||||||
public abstract class PlayerWrapper {
|
|
||||||
private enum Type { MediaPlayer, ExoPlayer }
|
|
||||||
|
|
||||||
private static final Type TYPE = Type.ExoPlayer;
|
|
||||||
private static final float DUCK_COEF = 0.2f; /* volume = 20% when ducked */
|
|
||||||
private static final float DUCK_NONE = -1.0f;
|
|
||||||
|
|
||||||
public enum State {
|
|
||||||
Stopped,
|
|
||||||
Preparing,
|
|
||||||
Prepared,
|
|
||||||
Playing,
|
|
||||||
Buffering,
|
|
||||||
Paused,
|
|
||||||
Error,
|
|
||||||
Finished,
|
|
||||||
Killing,
|
|
||||||
Disposed
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnStateChangedListener {
|
|
||||||
void onStateChanged(PlayerWrapper mpw, State state);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<PlayerWrapper> activePlayers = new HashSet<>();
|
|
||||||
private static float globalVolume = 1.0f;
|
|
||||||
private static boolean globalMuted = false;
|
|
||||||
private static float preDuckGlobalVolume = DUCK_NONE;
|
|
||||||
|
|
||||||
public static void duck() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (preDuckGlobalVolume == DUCK_NONE) {
|
|
||||||
final float lastVolume = globalVolume;
|
|
||||||
setGlobalVolume(globalVolume * DUCK_COEF);
|
|
||||||
preDuckGlobalVolume = lastVolume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unduck() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (preDuckGlobalVolume != DUCK_NONE) {
|
|
||||||
final float temp = preDuckGlobalVolume;
|
|
||||||
preDuckGlobalVolume = DUCK_NONE;
|
|
||||||
setGlobalVolume(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setGlobalVolume(float volume) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (preDuckGlobalVolume != DUCK_NONE) {
|
|
||||||
preDuckGlobalVolume = volume;
|
|
||||||
volume = volume * DUCK_COEF;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (volume != globalVolume) {
|
|
||||||
globalVolume = volume;
|
|
||||||
for (final PlayerWrapper w : activePlayers) {
|
|
||||||
w.updateVolume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float getGlobalVolume() {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (globalMuted) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (preDuckGlobalVolume == DUCK_NONE) ? globalVolume : preDuckGlobalVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setGlobalMute(final boolean muted) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
if (PlayerWrapper.globalMuted != muted) {
|
|
||||||
PlayerWrapper.globalMuted = muted;
|
|
||||||
|
|
||||||
for (final PlayerWrapper w : activePlayers) {
|
|
||||||
w.updateVolume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PlayerWrapper newInstance() {
|
|
||||||
return TYPE == Type.ExoPlayer
|
|
||||||
? new ExoPlayerWrapper()
|
|
||||||
: new MediaPlayerWrapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void addActivePlayer(final PlayerWrapper player) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
activePlayers.add(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void removeActivePlayer(final PlayerWrapper player) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
activePlayers.remove(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private OnStateChangedListener listener;
|
|
||||||
private State state = State.Stopped;
|
|
||||||
|
|
||||||
public abstract void play(final String uri);
|
|
||||||
public abstract void prefetch(final String uri);
|
|
||||||
public abstract void pause();
|
|
||||||
public abstract void resume();
|
|
||||||
public abstract void setPosition(int millis);
|
|
||||||
public abstract int getPosition();
|
|
||||||
public abstract int getDuration();
|
|
||||||
public abstract void updateVolume();
|
|
||||||
public abstract void setNextMediaPlayer(final PlayerWrapper wrapper);
|
|
||||||
public abstract void dispose();
|
|
||||||
public abstract int getBufferedPercent();
|
|
||||||
|
|
||||||
public void setOnStateChangedListener(OnStateChangedListener listener) {
|
|
||||||
Preconditions.throwIfNotOnMainThread();
|
|
||||||
|
|
||||||
this.listener = listener;
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
this.listener.onStateChanged(this, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final State getState() {
|
|
||||||
return this.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setState(final PlayerWrapper.State state) {
|
|
||||||
if (this.state != state) {
|
|
||||||
this.state = state;
|
|
||||||
if (listener != null) {
|
|
||||||
this.listener.onStateChanged(this, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,148 @@
|
|||||||
|
package io.casey.musikcube.remote.playback
|
||||||
|
|
||||||
|
import java.util.HashSet
|
||||||
|
|
||||||
|
import io.casey.musikcube.remote.util.Preconditions
|
||||||
|
|
||||||
|
abstract class PlayerWrapper {
|
||||||
|
private enum class Type {
|
||||||
|
MediaPlayer, ExoPlayer
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
Stopped,
|
||||||
|
Preparing,
|
||||||
|
Prepared,
|
||||||
|
Playing,
|
||||||
|
Buffering,
|
||||||
|
Paused,
|
||||||
|
Error,
|
||||||
|
Finished,
|
||||||
|
Killing,
|
||||||
|
Disposed
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnStateChangedListener {
|
||||||
|
fun onStateChanged(mpw: PlayerWrapper, state: State)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var listener: OnStateChangedListener? = null
|
||||||
|
var state = State.Stopped
|
||||||
|
protected set(state) {
|
||||||
|
if (this.state != state) {
|
||||||
|
field = state
|
||||||
|
if (listener != null) {
|
||||||
|
this.listener!!.onStateChanged(this, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun play(uri: String)
|
||||||
|
abstract fun prefetch(uri: String)
|
||||||
|
abstract fun pause()
|
||||||
|
abstract fun resume()
|
||||||
|
abstract fun updateVolume()
|
||||||
|
abstract fun setNextMediaPlayer(wrapper: PlayerWrapper?)
|
||||||
|
abstract fun dispose()
|
||||||
|
abstract var position: Int
|
||||||
|
abstract val duration: Int
|
||||||
|
abstract val bufferedPercent: Int
|
||||||
|
|
||||||
|
open fun setOnStateChangedListener(listener: OnStateChangedListener?) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
this.listener = listener
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
|
this.listener!!.onStateChanged(this, this.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TYPE = Type.ExoPlayer
|
||||||
|
private val DUCK_COEF = 0.2f /* volume = 20% when ducked */
|
||||||
|
private val DUCK_NONE = -1.0f
|
||||||
|
|
||||||
|
private val activePlayers = HashSet<PlayerWrapper>()
|
||||||
|
private var globalVolume = 1.0f
|
||||||
|
private var globalMuted = false
|
||||||
|
private var preDuckGlobalVolume = DUCK_NONE
|
||||||
|
|
||||||
|
fun duck() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (preDuckGlobalVolume == DUCK_NONE) {
|
||||||
|
val lastVolume = globalVolume
|
||||||
|
setVolume(globalVolume * DUCK_COEF)
|
||||||
|
preDuckGlobalVolume = lastVolume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unduck() {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (preDuckGlobalVolume != DUCK_NONE) {
|
||||||
|
val temp = preDuckGlobalVolume
|
||||||
|
preDuckGlobalVolume = DUCK_NONE
|
||||||
|
setVolume(temp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVolume(volume: Float) {
|
||||||
|
var volume = volume
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (preDuckGlobalVolume != DUCK_NONE) {
|
||||||
|
preDuckGlobalVolume = volume
|
||||||
|
volume = volume * DUCK_COEF
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume != globalVolume) {
|
||||||
|
globalVolume = volume
|
||||||
|
for (w in activePlayers) {
|
||||||
|
w.updateVolume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVolume(): Float {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (globalMuted) {
|
||||||
|
return 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (preDuckGlobalVolume == DUCK_NONE) globalVolume else preDuckGlobalVolume
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMute(muted: Boolean) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
|
||||||
|
if (PlayerWrapper.globalMuted != muted) {
|
||||||
|
PlayerWrapper.globalMuted = muted
|
||||||
|
|
||||||
|
for (w in activePlayers) {
|
||||||
|
w.updateVolume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newInstance(): PlayerWrapper {
|
||||||
|
return if (TYPE == Type.ExoPlayer)
|
||||||
|
ExoPlayerWrapper()
|
||||||
|
else
|
||||||
|
MediaPlayerWrapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addActivePlayer(player: PlayerWrapper) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
activePlayers.add(player)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeActivePlayer(player: PlayerWrapper) {
|
||||||
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
activePlayers.remove(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,12 +22,13 @@ public class StreamProxy {
|
|||||||
public static final boolean ENABLED = true;
|
public static final boolean ENABLED = true;
|
||||||
public static final long BYTES_PER_MEGABYTE = 1048576L;
|
public static final long BYTES_PER_MEGABYTE = 1048576L;
|
||||||
public static final long BYTES_PER_GIGABYTE = 1073741824L;
|
public static final long BYTES_PER_GIGABYTE = 1073741824L;
|
||||||
|
public static final long MINIMUM_CACHE_SIZE_BYTES = BYTES_PER_MEGABYTE * 32;
|
||||||
public static final Map<Integer, Long> CACHE_SETTING_TO_BYTES;
|
public static final Map<Integer, Long> CACHE_SETTING_TO_BYTES;
|
||||||
private static final FileNameGenerator DEFAULT_FILENAME_GENERATOR = new Md5FileNameGenerator();
|
private static final FileNameGenerator DEFAULT_FILENAME_GENERATOR = new Md5FileNameGenerator();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
CACHE_SETTING_TO_BYTES = new HashMap<>();
|
CACHE_SETTING_TO_BYTES = new HashMap<>();
|
||||||
CACHE_SETTING_TO_BYTES.put(0, BYTES_PER_MEGABYTE * 32);
|
CACHE_SETTING_TO_BYTES.put(0, MINIMUM_CACHE_SIZE_BYTES);
|
||||||
CACHE_SETTING_TO_BYTES.put(1, BYTES_PER_GIGABYTE / 2);
|
CACHE_SETTING_TO_BYTES.put(1, BYTES_PER_GIGABYTE / 2);
|
||||||
CACHE_SETTING_TO_BYTES.put(2, BYTES_PER_GIGABYTE);
|
CACHE_SETTING_TO_BYTES.put(2, BYTES_PER_GIGABYTE);
|
||||||
CACHE_SETTING_TO_BYTES.put(3, BYTES_PER_GIGABYTE * 2);
|
CACHE_SETTING_TO_BYTES.put(3, BYTES_PER_GIGABYTE * 2);
|
||||||
|
@ -330,7 +330,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
if (prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME)) {
|
if (prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME)) {
|
||||||
return PlayerWrapper.getGlobalVolume();
|
return PlayerWrapper.Companion.getVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSystemVolume();
|
return getSystemVolume();
|
||||||
@ -374,7 +374,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
@Override
|
@Override
|
||||||
public void toggleMute() {
|
public void toggleMute() {
|
||||||
muted = !muted;
|
muted = !muted;
|
||||||
PlayerWrapper.setGlobalMute(muted);
|
PlayerWrapper.Companion.setMute(muted);
|
||||||
notifyEventListeners();
|
notifyEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,14 +451,14 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final boolean softwareVolume = prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME);
|
final boolean softwareVolume = prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME);
|
||||||
float current = softwareVolume ? PlayerWrapper.getGlobalVolume() : getSystemVolume();
|
float current = softwareVolume ? PlayerWrapper.Companion.getVolume() : getSystemVolume();
|
||||||
|
|
||||||
current += delta;
|
current += delta;
|
||||||
if (current > 1.0) current = 1.0f;
|
if (current > 1.0) current = 1.0f;
|
||||||
if (current < 0.0) current = 0.0f;
|
if (current < 0.0) current = 0.0f;
|
||||||
|
|
||||||
if (softwareVolume) {
|
if (softwareVolume) {
|
||||||
PlayerWrapper.setGlobalVolume(current);
|
PlayerWrapper.Companion.setVolume(current);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final int actual = Math.round(current * getMaxSystemVolume());
|
final int actual = Math.round(current * getMaxSystemVolume());
|
||||||
@ -699,7 +699,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
this.context.reset(this.context.nextPlayer);
|
this.context.reset(this.context.nextPlayer);
|
||||||
this.context.nextPlayer = PlayerWrapper.newInstance();
|
this.context.nextPlayer = PlayerWrapper.Companion.newInstance();
|
||||||
this.context.nextPlayer.setOnStateChangedListener(onNextPlayerStateChanged);
|
this.context.nextPlayer.setOnStateChangedListener(onNextPlayerStateChanged);
|
||||||
this.context.nextPlayer.prefetch(uri);
|
this.context.nextPlayer.prefetch(uri);
|
||||||
}
|
}
|
||||||
@ -792,7 +792,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
|
|
||||||
final String uri = getUri(this.context.currentMetadata);
|
final String uri = getUri(this.context.currentMetadata);
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
this.context.currentPlayer = PlayerWrapper.newInstance();
|
this.context.currentPlayer = PlayerWrapper.Companion.newInstance();
|
||||||
this.context.currentPlayer.setOnStateChangedListener(onCurrentPlayerStateChanged);
|
this.context.currentPlayer.setOnStateChangedListener(onCurrentPlayerStateChanged);
|
||||||
this.context.currentPlayer.play(uri);
|
this.context.currentPlayer.play(uri);
|
||||||
}
|
}
|
||||||
@ -912,7 +912,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
|
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
|
||||||
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
|
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
|
||||||
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
|
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
|
||||||
PlayerWrapper.unduck();
|
PlayerWrapper.Companion.unduck();
|
||||||
if (pausedByTransientLoss) {
|
if (pausedByTransientLoss) {
|
||||||
pausedByTransientLoss = false;
|
pausedByTransientLoss = false;
|
||||||
resume();
|
resume();
|
||||||
@ -934,7 +934,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||||
PlayerWrapper.duck();
|
PlayerWrapper.Companion.duck();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -22,8 +22,8 @@ import android.widget.Spinner;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import io.casey.musikcube.remote.R;
|
import io.casey.musikcube.remote.R;
|
||||||
import io.casey.musikcube.remote.playback.MediaPlayerWrapper;
|
|
||||||
import io.casey.musikcube.remote.playback.PlaybackServiceFactory;
|
import io.casey.musikcube.remote.playback.PlaybackServiceFactory;
|
||||||
|
import io.casey.musikcube.remote.playback.PlayerWrapper;
|
||||||
import io.casey.musikcube.remote.playback.StreamProxy;
|
import io.casey.musikcube.remote.playback.StreamProxy;
|
||||||
import io.casey.musikcube.remote.ui.util.Views;
|
import io.casey.musikcube.remote.ui.util.Views;
|
||||||
import io.casey.musikcube.remote.websocket.Prefs;
|
import io.casey.musikcube.remote.websocket.Prefs;
|
||||||
@ -204,7 +204,7 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
if (!softwareVolume.isChecked()) {
|
if (!softwareVolume.isChecked()) {
|
||||||
MediaPlayerWrapper.setGlobalVolume(1.0f);
|
PlayerWrapper.Companion.setVolume(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasStreaming && !isStreamingEnabled()) {
|
if (wasStreaming && !isStreamingEnabled()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user