ananas/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt
2021-09-18 21:11:45 +02:00

228 lines
No EOL
7.9 KiB
Kotlin

package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@HiltViewModel
class PlayerActivityViewModel
@Inject
constructor(
application: Application,
private val jellyfinRepository: JellyfinRepository
) : ViewModel(), Player.Listener {
val player: BasePlayer
private val _navigateBack = MutableLiveData<Boolean>()
val navigateBack: LiveData<Boolean> = _navigateBack
private val _currentItemTitle = MutableLiveData<String>()
val currentItemTitle: LiveData<String> = _currentItemTitle
var currentAudioTracks: MutableList<MPVPlayer.Companion.Track> = mutableListOf()
var currentSubtitleTracks: MutableList<MPVPlayer.Companion.Track> = mutableListOf()
private val _fileLoaded = MutableLiveData(false)
val fileLoaded: LiveData<Boolean> = _fileLoaded
private var items: Array<PlayerItem> = arrayOf()
val trackSelector = DefaultTrackSelector(application)
var playWhenReady = true
private var currentWindow = 0
private var playbackPosition: Long = 0
private val sp = PreferenceManager.getDefaultSharedPreferences(application)
init {
val useMpv = sp.getBoolean("mpv_player", false)
val preferredAudioLanguage = sp.getString("audio_language", null) ?: ""
val preferredSubtitleLanguage = sp.getString("subtitle_language", null) ?: ""
if (useMpv) {
val preferredLanguages = mapOf(
TrackType.AUDIO to preferredAudioLanguage,
TrackType.SUBTITLE to preferredSubtitleLanguage
)
player = MPVPlayer(
application,
false,
preferredLanguages,
sp.getBoolean("mpv_disable_hwdec", false)
)
} else {
val renderersFactory =
DefaultRenderersFactory(application).setExtensionRendererMode(
DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
)
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setTunnelingEnabled(true)
.setPreferredAudioLanguage(preferredAudioLanguage)
.setPreferredTextLanguage(preferredSubtitleLanguage)
)
player = SimpleExoPlayer.Builder(application, renderersFactory)
.setTrackSelector(trackSelector)
.build()
}
}
fun initializePlayer(
items: Array<PlayerItem>
) {
this.items = items
player.addListener(this)
viewModelScope.launch {
val mediaItems: MutableList<MediaItem> = mutableListOf()
try {
for (item in items) {
val streamUrl = jellyfinRepository.getStreamUrl(item.itemId, item.mediaSourceId)
Timber.d("Stream url: $streamUrl")
val mediaItem =
MediaItem.Builder()
.setMediaId(item.itemId.toString())
.setUri(streamUrl)
.build()
mediaItems.add(mediaItem)
}
} catch (e: Exception) {
Timber.e(e)
}
player.setMediaItems(mediaItems, currentWindow, items[0].playbackPosition)
player.prepare()
player.play()
}
pollPosition(player)
}
private fun releasePlayer() {
player.let { player ->
runBlocking {
try {
jellyfinRepository.postPlaybackStop(
UUID.fromString(player.currentMediaItem?.mediaId),
player.currentPosition.times(10000)
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
playWhenReady = player.playWhenReady
playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex
player.removeListener(this)
player.release()
}
private fun pollPosition(player: BasePlayer) {
val handler = Handler(Looper.getMainLooper())
val runnable = object : Runnable {
override fun run() {
viewModelScope.launch {
if (player.currentMediaItem != null) {
try {
jellyfinRepository.postPlaybackProgress(
UUID.fromString(player.currentMediaItem!!.mediaId),
player.currentPosition.times(10000),
!player.isPlaying
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
handler.postDelayed(this, 2000)
}
}
handler.post(runnable)
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
Timber.d("Playing MediaItem: ${mediaItem?.mediaId}")
viewModelScope.launch {
try {
for (item in items) {
if (item.itemId.toString() == player.currentMediaItem?.mediaId ?: "") {
_currentItemTitle.value = item.name
}
}
jellyfinRepository.postPlaybackStart(UUID.fromString(mediaItem?.mediaId))
} catch (e: Exception) {
Timber.e(e)
}
}
}
override fun onPlaybackStateChanged(state: Int) {
var stateString = "UNKNOWN_STATE -"
when (state) {
ExoPlayer.STATE_IDLE -> {
stateString = "ExoPlayer.STATE_IDLE -"
}
ExoPlayer.STATE_BUFFERING -> {
stateString = "ExoPlayer.STATE_BUFFERING -"
}
ExoPlayer.STATE_READY -> {
stateString = "ExoPlayer.STATE_READY -"
currentAudioTracks.clear()
currentSubtitleTracks.clear()
when (player) {
is MPVPlayer -> {
player.currentTracks.forEach {
when (it.type) {
TrackType.AUDIO -> {
currentAudioTracks.add(it)
}
TrackType.SUBTITLE -> {
currentSubtitleTracks.add(it)
}
}
}
}
}
_fileLoaded.value = true
}
ExoPlayer.STATE_ENDED -> {
stateString = "ExoPlayer.STATE_ENDED -"
_navigateBack.value = true
}
}
Timber.d("Changed player state to $stateString")
}
override fun onCleared() {
super.onCleared()
Timber.d("Clearing Player ViewModel")
releasePlayer()
}
fun switchToTrack(trackType: String, track: MPVPlayer.Companion.Track) {
if (player is MPVPlayer) {
player.selectTrack(trackType, isExternal = false, index = track.ffIndex)
}
}
}