mirror of
https://github.com/clangen/musikcube.git
synced 2025-02-24 03:40:03 +00:00
Removed MediaPlayerWrapper, ExoPlayerWrapper. GaplessExoPlayerWrapper is
the way forward!
This commit is contained in:
parent
5b356c4991
commit
b96934dbfc
@ -6,9 +6,7 @@ import io.casey.musikcube.remote.injection.DaggerPlaybackComponent
|
|||||||
import io.casey.musikcube.remote.service.gapless.GaplessHeaderService
|
import io.casey.musikcube.remote.service.gapless.GaplessHeaderService
|
||||||
import io.casey.musikcube.remote.service.gapless.db.GaplessDb
|
import io.casey.musikcube.remote.service.gapless.db.GaplessDb
|
||||||
import io.casey.musikcube.remote.service.gapless.db.GaplessTrack
|
import io.casey.musikcube.remote.service.gapless.db.GaplessTrack
|
||||||
import io.casey.musikcube.remote.service.playback.impl.player.ExoPlayerWrapper
|
|
||||||
import io.casey.musikcube.remote.service.playback.impl.player.GaplessExoPlayerWrapper
|
import io.casey.musikcube.remote.service.playback.impl.player.GaplessExoPlayerWrapper
|
||||||
import io.casey.musikcube.remote.service.playback.impl.player.MediaPlayerWrapper
|
|
||||||
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
|
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
|
||||||
import io.casey.musikcube.remote.service.playback.impl.streaming.db.OfflineDb
|
import io.casey.musikcube.remote.service.playback.impl.streaming.db.OfflineDb
|
||||||
import io.casey.musikcube.remote.service.playback.impl.streaming.db.OfflineTrack
|
import io.casey.musikcube.remote.service.playback.impl.streaming.db.OfflineTrack
|
||||||
@ -33,19 +31,6 @@ abstract class PlayerWrapper {
|
|||||||
.build().inject(this)
|
.build().inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Type(prefIndex: Int) {
|
|
||||||
ExoPlayer(0), ExoPlayerGapless(1), MediaPlayer(2);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromPrefIndex(index: Int): Type =
|
|
||||||
when(index) {
|
|
||||||
2 -> MediaPlayer
|
|
||||||
1 -> ExoPlayerGapless
|
|
||||||
else -> ExoPlayer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
Stopped,
|
Stopped,
|
||||||
Preparing,
|
Preparing,
|
||||||
@ -108,8 +93,8 @@ abstract class PlayerWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DUCK_COEF = 0.2f /* volume = 20% when ducked */
|
private const val DUCK_COEF = 0.2f /* volume = 20% when ducked */
|
||||||
private val DUCK_NONE = -1.0f
|
private const val DUCK_NONE = -1.0f
|
||||||
|
|
||||||
private val activePlayers = HashSet<PlayerWrapper>()
|
private val activePlayers = HashSet<PlayerWrapper>()
|
||||||
private var globalVolume = 1.0f
|
private var globalVolume = 1.0f
|
||||||
@ -175,17 +160,7 @@ abstract class PlayerWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newInstance(prefs: SharedPreferences): PlayerWrapper {
|
fun newInstance(): PlayerWrapper = GaplessExoPlayerWrapper()
|
||||||
val type = prefs.getInt(
|
|
||||||
Prefs.Key.PLAYBACK_ENGINE_INDEX,
|
|
||||||
Prefs.Default.PLAYBACK_ENGINE_INDEX)
|
|
||||||
|
|
||||||
return when (Type.fromPrefIndex(type)) {
|
|
||||||
Type.ExoPlayer -> ExoPlayerWrapper()
|
|
||||||
Type.ExoPlayerGapless -> GaplessExoPlayerWrapper()
|
|
||||||
Type.MediaPlayer -> MediaPlayerWrapper()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addActivePlayer(player: PlayerWrapper) {
|
fun addActivePlayer(player: PlayerWrapper) {
|
||||||
Preconditions.throwIfNotOnMainThread()
|
Preconditions.throwIfNotOnMainThread()
|
||||||
|
@ -1,309 +0,0 @@
|
|||||||
package io.casey.musikcube.remote.service.playback.impl.player
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
|
||||||
import android.util.Log
|
|
||||||
import com.danikula.videocache.CacheListener
|
|
||||||
import com.google.android.exoplayer2.*
|
|
||||||
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.service.playback.PlayerWrapper
|
|
||||||
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
|
|
||||||
import io.casey.musikcube.remote.service.websocket.model.ITrack
|
|
||||||
import io.casey.musikcube.remote.ui.settings.constants.Prefs
|
|
||||||
import io.casey.musikcube.remote.util.Preconditions
|
|
||||||
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 metadata: ITrack? = null
|
|
||||||
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 proxyUri: String? = null
|
|
||||||
private val transcoding: Boolean
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.context = Application.instance!!
|
|
||||||
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.instance!!.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
|
|
||||||
this.transcoding = this.prefs.getInt(Prefs.Key.TRANSCODER_BITRATE_INDEX, 0) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun play(uri: String, metadata: ITrack, offsetMs: Int) {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
this.metadata = metadata
|
|
||||||
this.originalUri = uri
|
|
||||||
this.proxyUri = streamProxy.getProxyUrl(uri)
|
|
||||||
|
|
||||||
Log.d("ExoPlayerWrapper", "originalUri: ${this.originalUri} proxyUri: ${this.proxyUri}")
|
|
||||||
|
|
||||||
addCacheListener()
|
|
||||||
|
|
||||||
this.source = ExtractorMediaSource(Uri.parse(proxyUri), datasources, extractors, null, null)
|
|
||||||
this.player!!.playWhenReady = true
|
|
||||||
this.player.prepare(this.source)
|
|
||||||
addActivePlayer(this)
|
|
||||||
state = State.Preparing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun prefetch(uri: String, metadata: ITrack) {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
this.metadata = metadata
|
|
||||||
this.originalUri = uri
|
|
||||||
this.proxyUri = streamProxy.getProxyUrl(uri)
|
|
||||||
Log.d("ExoPlayerWrapper", "originalUri: ${this.originalUri} proxyUri: ${this.proxyUri}")
|
|
||||||
|
|
||||||
this.prefetch = true
|
|
||||||
|
|
||||||
addCacheListener()
|
|
||||||
|
|
||||||
this.source = ExtractorMediaSource(Uri.parse(proxyUri), datasources, extractors, null, null)
|
|
||||||
this.player!!.playWhenReady = false
|
|
||||||
this.player.prepare(this.source)
|
|
||||||
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()
|
|
||||||
|
|
||||||
prefetch = false
|
|
||||||
|
|
||||||
when (state) {
|
|
||||||
State.Paused,
|
|
||||||
State.Prepared -> {
|
|
||||||
player!!.playWhenReady = true
|
|
||||||
state = State.Playing
|
|
||||||
}
|
|
||||||
|
|
||||||
State.Error -> {
|
|
||||||
player!!.playWhenReady = lastPosition == -1L
|
|
||||||
player.prepare(source)
|
|
||||||
state = State.Preparing
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> { }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override val uri get() = originalUri ?: ""
|
|
||||||
|
|
||||||
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 = getVolume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setNextMediaPlayer(wrapper: PlayerWrapper?) {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
if (!dead()) {
|
|
||||||
state = State.Killing
|
|
||||||
removeActivePlayer(this)
|
|
||||||
removeCacheListener()
|
|
||||||
this.player?.playWhenReady = false
|
|
||||||
this.player?.removeListener(eventListener)
|
|
||||||
this.player?.stop()
|
|
||||||
this.player?.release()
|
|
||||||
state = State.Disposed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dead(): Boolean {
|
|
||||||
val state = state
|
|
||||||
return state == State.Killing || state == State.Disposed
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addCacheListener() {
|
|
||||||
if (streamProxy.isCached(this.originalUri!!)) {
|
|
||||||
percentAvailable = 100
|
|
||||||
|
|
||||||
if (originalUri != null && metadata != null) {
|
|
||||||
storeOffline(originalUri!!, metadata!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamProxy.registerCacheListener(this.cacheListener, this.originalUri!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeCacheListener() {
|
|
||||||
streamProxy.unregisterCacheListener(this.cacheListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cacheListener = CacheListener { _: File, _: String, percent: Int ->
|
|
||||||
percentAvailable = percent
|
|
||||||
|
|
||||||
if (percentAvailable >= 100) {
|
|
||||||
if (originalUri != null && metadata != null) {
|
|
||||||
storeOffline(originalUri!!, metadata!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var eventListener = object : Player.EventListener {
|
|
||||||
override fun onTimelineChanged(timeline: Timeline, manifest: Any?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadingChanged(isLoading: Boolean) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSeekProcessed() {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: 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 = 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
|
|
||||||
else -> { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPositionDiscontinuity(type: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.player!!.addListener(eventListener)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
package io.casey.musikcube.remote.service.playback.impl.player
|
|
||||||
|
|
||||||
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 io.casey.musikcube.remote.Application
|
|
||||||
import io.casey.musikcube.remote.service.websocket.model.ITrack
|
|
||||||
import io.casey.musikcube.remote.service.playback.PlayerWrapper
|
|
||||||
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
|
|
||||||
import io.casey.musikcube.remote.util.Preconditions
|
|
||||||
import io.casey.musikcube.remote.ui.settings.constants.Prefs
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class MediaPlayerWrapper : PlayerWrapper() {
|
|
||||||
private val player = MediaPlayer()
|
|
||||||
private var seekTo: Int = 0
|
|
||||||
private var prefetching: Boolean = false
|
|
||||||
private val context = Application.instance
|
|
||||||
private val prefs: SharedPreferences
|
|
||||||
private var metadata: ITrack? = null
|
|
||||||
private var proxyUri: String? = null
|
|
||||||
private var originalUri: String? = null
|
|
||||||
override var bufferedPercent: Int = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.prefs = context!!.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun play(uri: String, metadata: ITrack, offsetMs: Int) {
|
|
||||||
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)
|
|
||||||
|
|
||||||
this.metadata = metadata
|
|
||||||
this.originalUri = uri
|
|
||||||
this.proxyUri = streamProxy.getProxyUrl(uri)
|
|
||||||
|
|
||||||
player.setDataSource(context, Uri.parse(proxyUri), headers)
|
|
||||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC)
|
|
||||||
player.setOnPreparedListener(onPrepared)
|
|
||||||
player.setOnErrorListener(onError)
|
|
||||||
player.setOnCompletionListener(onCompleted)
|
|
||||||
player.setOnBufferingUpdateListener(onBuffering)
|
|
||||||
player.setWakeMode(Application.instance, PowerManager.PARTIAL_WAKE_LOCK)
|
|
||||||
player.prepareAsync()
|
|
||||||
}
|
|
||||||
catch (e: IOException) {
|
|
||||||
Log.e(TAG, "setDataSource failed: " + e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun prefetch(uri: String, metadata: ITrack) {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
this.prefetching = true
|
|
||||||
play(uri, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pause() {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
if (isPreparedOrPlaying) {
|
|
||||||
player.pause()
|
|
||||||
state = State.Paused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override var position: Int
|
|
||||||
get() {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
return if (isPreparedOrPlaying) this.player.currentPosition else 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()
|
|
||||||
return if (isPreparedOrPlaying) this.player.duration else 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 = getVolume()
|
|
||||||
player.setVolume(volume, volume)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val uri get() = originalUri ?: ""
|
|
||||||
|
|
||||||
private val isPreparedOrPlaying: Boolean
|
|
||||||
get() = (state === State.Playing || state === State.Prepared)
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
Preconditions.throwIfNotOnMainThread()
|
|
||||||
|
|
||||||
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 ->
|
|
||||||
if (this.state === State.Killing) {
|
|
||||||
dispose()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val volume = getVolume()
|
|
||||||
player.setVolume(volume, volume)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if (bufferedPercent >= 100) {
|
|
||||||
if (originalUri != null && metadata != null) {
|
|
||||||
storeOffline(originalUri!!, metadata!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = "MediaPlayerWrapper"
|
|
||||||
}
|
|
||||||
}
|
|
@ -642,7 +642,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
|
|||||||
|
|
||||||
if (uri != null && uri != playContext.nextPlayer?.uri) {
|
if (uri != null && uri != playContext.nextPlayer?.uri) {
|
||||||
playContext.reset(playContext.nextPlayer)
|
playContext.reset(playContext.nextPlayer)
|
||||||
playContext.nextPlayer = PlayerWrapper.newInstance(prefs)
|
playContext.nextPlayer = PlayerWrapper.newInstance()
|
||||||
playContext.nextPlayer?.setOnStateChangedListener(onNextPlayerStateChanged)
|
playContext.nextPlayer?.setOnStateChangedListener(onNextPlayerStateChanged)
|
||||||
playContext.nextPlayer?.prefetch(uri, playContext.nextMetadata!!)
|
playContext.nextPlayer?.prefetch(uri, playContext.nextMetadata!!)
|
||||||
}
|
}
|
||||||
@ -740,7 +740,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
|
|||||||
val uri = getUri(playContext.currentMetadata)
|
val uri = getUri(playContext.currentMetadata)
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
playContext.currentPlayer = PlayerWrapper.newInstance(prefs)
|
playContext.currentPlayer = PlayerWrapper.newInstance()
|
||||||
playContext.currentPlayer?.setOnStateChangedListener(onCurrentPlayerStateChanged)
|
playContext.currentPlayer?.setOnStateChangedListener(onCurrentPlayerStateChanged)
|
||||||
playContext.currentPlayer?.play(uri, playContext.currentMetadata!!, offsetMs)
|
playContext.currentPlayer?.play(uri, playContext.currentMetadata!!, offsetMs)
|
||||||
}
|
}
|
||||||
|
@ -45,13 +45,10 @@ class SettingsActivity : BaseActivity() {
|
|||||||
private lateinit var certCheckbox: CheckBox
|
private lateinit var certCheckbox: CheckBox
|
||||||
private lateinit var bitrateSpinner: Spinner
|
private lateinit var bitrateSpinner: Spinner
|
||||||
private lateinit var cacheSpinner: Spinner
|
private lateinit var cacheSpinner: Spinner
|
||||||
private lateinit var playbackEngineSpinner: Spinner
|
|
||||||
private lateinit var prefs: SharedPreferences
|
private lateinit var prefs: SharedPreferences
|
||||||
private lateinit var playback: PlaybackMixin
|
private lateinit var playback: PlaybackMixin
|
||||||
private lateinit var data: DataProviderMixin
|
private lateinit var data: DataProviderMixin
|
||||||
|
|
||||||
private var engineType = -1
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
data = mixin(DataProviderMixin())
|
data = mixin(DataProviderMixin())
|
||||||
playback = mixin(PlaybackMixin())
|
playback = mixin(PlaybackMixin())
|
||||||
@ -142,21 +139,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
cacheSpinner.setSelection(prefs.getInt(
|
cacheSpinner.setSelection(prefs.getInt(
|
||||||
Keys.DISK_CACHE_SIZE_INDEX, Defaults.DISK_CACHE_SIZE_INDEX))
|
Keys.DISK_CACHE_SIZE_INDEX, Defaults.DISK_CACHE_SIZE_INDEX))
|
||||||
|
|
||||||
/* playback engine */
|
|
||||||
val engines = ArrayAdapter.createFromResource(
|
|
||||||
this, R.array.playback_engine_array, android.R.layout.simple_spinner_item)
|
|
||||||
|
|
||||||
engines.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
|
|
||||||
val engineType = prefs.getInt(Keys.PLAYBACK_ENGINE_INDEX, Defaults.PLAYBACK_ENGINE_INDEX)
|
|
||||||
|
|
||||||
if (this.engineType == -1) {
|
|
||||||
this.engineType = engineType
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackEngineSpinner.adapter = engines
|
|
||||||
playbackEngineSpinner.setSelection(engineType)
|
|
||||||
|
|
||||||
/* advanced */
|
/* advanced */
|
||||||
albumArtCheckbox.isChecked = prefs.getBoolean(
|
albumArtCheckbox.isChecked = prefs.getBoolean(
|
||||||
Keys.LASTFM_ENABLED, Defaults.LASTFM_ENABLED)
|
Keys.LASTFM_ENABLED, Defaults.LASTFM_ENABLED)
|
||||||
@ -212,7 +194,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
this.softwareVolume = findViewById(R.id.software_volume)
|
this.softwareVolume = findViewById(R.id.software_volume)
|
||||||
this.bitrateSpinner = findViewById(R.id.transcoder_bitrate_spinner)
|
this.bitrateSpinner = findViewById(R.id.transcoder_bitrate_spinner)
|
||||||
this.cacheSpinner = findViewById(R.id.streaming_disk_cache_spinner)
|
this.cacheSpinner = findViewById(R.id.streaming_disk_cache_spinner)
|
||||||
this.playbackEngineSpinner = findViewById(R.id.streaming_playback_engine)
|
|
||||||
this.sslCheckbox = findViewById(R.id.ssl_checkbox)
|
this.sslCheckbox = findViewById(R.id.ssl_checkbox)
|
||||||
this.certCheckbox = findViewById(R.id.cert_validation)
|
this.certCheckbox = findViewById(R.id.cert_validation)
|
||||||
}
|
}
|
||||||
@ -271,12 +252,6 @@ class SettingsActivity : BaseActivity() {
|
|||||||
val password = passwordText.text.toString()
|
val password = passwordText.text.toString()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val engineType = playbackEngineSpinner.selectedItemPosition
|
|
||||||
|
|
||||||
val streaming = prefs.getBoolean(
|
|
||||||
Prefs.Key.STREAMING_PLAYBACK,
|
|
||||||
Prefs.Default.STREAMING_PLAYBACK)
|
|
||||||
|
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(Keys.ADDRESS, addr)
|
.putString(Keys.ADDRESS, addr)
|
||||||
.putInt(Keys.MAIN_PORT, if (port.isNotEmpty()) port.toInt() else 0)
|
.putInt(Keys.MAIN_PORT, if (port.isNotEmpty()) port.toInt() else 0)
|
||||||
@ -289,17 +264,12 @@ class SettingsActivity : BaseActivity() {
|
|||||||
.putBoolean(Keys.CERT_VALIDATION_DISABLED, certCheckbox.isChecked)
|
.putBoolean(Keys.CERT_VALIDATION_DISABLED, certCheckbox.isChecked)
|
||||||
.putInt(Keys.TRANSCODER_BITRATE_INDEX, bitrateSpinner.selectedItemPosition)
|
.putInt(Keys.TRANSCODER_BITRATE_INDEX, bitrateSpinner.selectedItemPosition)
|
||||||
.putInt(Keys.DISK_CACHE_SIZE_INDEX, cacheSpinner.selectedItemPosition)
|
.putInt(Keys.DISK_CACHE_SIZE_INDEX, cacheSpinner.selectedItemPosition)
|
||||||
.putInt(Keys.PLAYBACK_ENGINE_INDEX, engineType)
|
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
if (!softwareVolume.isChecked) {
|
if (!softwareVolume.isChecked) {
|
||||||
PlayerWrapper.setVolume(1.0f)
|
PlayerWrapper.setVolume(1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streaming && engineType != this.engineType) {
|
|
||||||
playback.service.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
streamProxy.reload()
|
streamProxy.reload()
|
||||||
data.wss.disconnect()
|
data.wss.disconnect()
|
||||||
|
|
||||||
@ -352,8 +322,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LEARN_MORE_URL = "https://github.com/clangen/musikcube/wiki/ssl-server-setup"
|
private const val LEARN_MORE_URL = "https://github.com/clangen/musikcube/wiki/ssl-server-setup"
|
||||||
val TAG = "ssl_alert_dialog_tag"
|
const val TAG = "ssl_alert_dialog_tag"
|
||||||
|
|
||||||
fun newInstance(): SslAlertDialog {
|
fun newInstance(): SslAlertDialog {
|
||||||
return SslAlertDialog()
|
return SslAlertDialog()
|
||||||
@ -377,7 +347,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "disable_cert_verify_dialog"
|
const val TAG = "disable_cert_verify_dialog"
|
||||||
|
|
||||||
fun newInstance(): DisableCertValidationAlertDialog {
|
fun newInstance(): DisableCertValidationAlertDialog {
|
||||||
return DisableCertValidationAlertDialog()
|
return DisableCertValidationAlertDialog()
|
||||||
@ -398,8 +368,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "invalid_connection_dialog"
|
const val TAG = "invalid_connection_dialog"
|
||||||
private val EXTRA_MESSAGE_ID = "extra_message_id"
|
private const val EXTRA_MESSAGE_ID = "extra_message_id"
|
||||||
fun newInstance(messageId: Int = R.string.settings_invalid_connection_message): InvalidConnectionDialog {
|
fun newInstance(messageId: Int = R.string.settings_invalid_connection_message): InvalidConnectionDialog {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putInt(EXTRA_MESSAGE_ID, messageId)
|
args.putInt(EXTRA_MESSAGE_ID, messageId)
|
||||||
@ -429,8 +399,8 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "confirm_overwrite_dialog"
|
const val TAG = "confirm_overwrite_dialog"
|
||||||
private val EXTRA_CONNECTION = "extra_connection"
|
private const val EXTRA_CONNECTION = "extra_connection"
|
||||||
|
|
||||||
fun newInstance(connection: Connection): ConfirmOverwriteDialog {
|
fun newInstance(connection: Connection): ConfirmOverwriteDialog {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
@ -473,7 +443,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "save_as_dialog"
|
const val TAG = "save_as_dialog"
|
||||||
|
|
||||||
fun newInstance(): SaveAsDialog {
|
fun newInstance(): SaveAsDialog {
|
||||||
return SaveAsDialog()
|
return SaveAsDialog()
|
||||||
@ -482,7 +452,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CONNECTIONS_REQUEST_CODE = 1000
|
const val CONNECTIONS_REQUEST_CODE = 1000
|
||||||
|
|
||||||
fun getStartIntent(context: Context): Intent {
|
fun getStartIntent(context: Context): Intent {
|
||||||
return Intent(context, SettingsActivity::class.java)
|
return Intent(context, SettingsActivity::class.java)
|
||||||
@ -512,7 +482,7 @@ private class SaveAsTask(val db: ConnectionsDb,
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val NAME = "SaveAsTask"
|
const val NAME = "SaveAsTask"
|
||||||
|
|
||||||
fun nameFor(connection: Connection): String {
|
fun nameFor(connection: Connection): String {
|
||||||
return "$NAME.${connection.name}"
|
return "$NAME.${connection.name}"
|
||||||
|
@ -3,42 +3,40 @@ package io.casey.musikcube.remote.ui.settings.constants
|
|||||||
class Prefs {
|
class Prefs {
|
||||||
interface Key {
|
interface Key {
|
||||||
companion object {
|
companion object {
|
||||||
val ADDRESS = "address"
|
const val ADDRESS = "address"
|
||||||
val MAIN_PORT = "port"
|
const val MAIN_PORT = "port"
|
||||||
val AUDIO_PORT = "http_port"
|
const val AUDIO_PORT = "http_port"
|
||||||
val PASSWORD = "password"
|
const val PASSWORD = "password"
|
||||||
val LASTFM_ENABLED = "lastfm_enabled"
|
const val LASTFM_ENABLED = "lastfm_enabled"
|
||||||
val MESSAGE_COMPRESSION_ENABLED = "message_compression_enabled"
|
const val MESSAGE_COMPRESSION_ENABLED = "message_compression_enabled"
|
||||||
val STREAMING_PLAYBACK = "streaming_playback"
|
const val STREAMING_PLAYBACK = "streaming_playback"
|
||||||
val SOFTWARE_VOLUME = "software_volume"
|
const val SOFTWARE_VOLUME = "software_volume"
|
||||||
val SSL_ENABLED = "ssl_enabled"
|
const val SSL_ENABLED = "ssl_enabled"
|
||||||
val CERT_VALIDATION_DISABLED = "cert_validation_disabled"
|
const val CERT_VALIDATION_DISABLED = "cert_validation_disabled"
|
||||||
val TRANSCODER_BITRATE_INDEX = "transcoder_bitrate_index"
|
const val TRANSCODER_BITRATE_INDEX = "transcoder_bitrate_index"
|
||||||
val DISK_CACHE_SIZE_INDEX = "disk_cache_size_index"
|
const val DISK_CACHE_SIZE_INDEX = "disk_cache_size_index"
|
||||||
val UPDATE_DIALOG_SUPPRESSED_VERSION = "update_dialog_suppressed_version"
|
const val UPDATE_DIALOG_SUPPRESSED_VERSION = "update_dialog_suppressed_version"
|
||||||
val PLAYBACK_ENGINE_INDEX = "playback_engine_index"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Default {
|
interface Default {
|
||||||
companion object {
|
companion object {
|
||||||
val ADDRESS = "192.168.1.100"
|
const val ADDRESS = "192.168.1.100"
|
||||||
val MAIN_PORT = 7905
|
const val MAIN_PORT = 7905
|
||||||
val AUDIO_PORT = 7906
|
const val AUDIO_PORT = 7906
|
||||||
val PASSWORD = ""
|
const val PASSWORD = ""
|
||||||
val LASTFM_ENABLED = true
|
const val LASTFM_ENABLED = true
|
||||||
val MESSAGE_COMPRESSION_ENABLED = true
|
const val MESSAGE_COMPRESSION_ENABLED = true
|
||||||
val STREAMING_PLAYBACK = false
|
const val STREAMING_PLAYBACK = false
|
||||||
val SOFTWARE_VOLUME = false
|
const val SOFTWARE_VOLUME = false
|
||||||
val SSL_ENABLED = false
|
const val SSL_ENABLED = false
|
||||||
val CERT_VALIDATION_DISABLED = false
|
const val CERT_VALIDATION_DISABLED = false
|
||||||
val TRANSCODER_BITRATE_INDEX = 0
|
const val TRANSCODER_BITRATE_INDEX = 0
|
||||||
val DISK_CACHE_SIZE_INDEX = 2
|
const val DISK_CACHE_SIZE_INDEX = 2
|
||||||
val PLAYBACK_ENGINE_INDEX = 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val NAME = "prefs"
|
const val NAME = "prefs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,23 +168,6 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="16dp"/>
|
android:layout_height="16dp"/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:text="@string/settings_playback_engine"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/streaming_playback_engine"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginLeft="24dp"/>
|
|
||||||
|
|
||||||
<android.support.v4.widget.Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string-array name="playback_engine_array">
|
|
||||||
<item>@string/settings_playback_engine_exo</item>
|
|
||||||
<item>@string/settings_playback_engine_exo_gapless</item>
|
|
||||||
<item>@string/settings_playback_engine_mp</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
|
Loading…
x
Reference in New Issue
Block a user