Start of chromecast support

This commit is contained in:
Rhys Davies 2022-08-08 13:09:42 +12:00
parent 978aed5498
commit 0a666c49c5
No known key found for this signature in database
18 changed files with 241 additions and 5 deletions

View file

@ -106,6 +106,8 @@ dependencies {
implementation(libs.jellyfin.core) implementation(libs.jellyfin.core)
compileOnly(libs.libmpv) compileOnly(libs.libmpv)
implementation(libs.material) implementation(libs.material)
implementation(libs.mediarouter)
implementation(libs.playServicesCastFramework)
implementation(libs.timber) implementation(libs.timber)
implementation(rootProject.files("libs/lib-decoder-ffmpeg-release.aar")) implementation(rootProject.files("libs/lib-decoder-ffmpeg-release.aar"))

View file

@ -33,6 +33,23 @@
</activity> </activity>
<!-- android:theme="@style/Theme.CastVideosDark"-->
<activity
android:name=".chromecast.ExpandedControlsActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:parentActivityName=".PlayerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="dev.jdtech.jellyfin.chromecast.CastOptionsProvider" />
</application> </application>
</manifest> </manifest>

View file

@ -11,20 +11,31 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.NavigationUiSaveStateControl import androidx.navigation.ui.NavigationUiSaveStateControl
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManager
import com.google.android.gms.cast.framework.SessionManagerListener
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.databinding.ActivityMainBinding import dev.jdtech.jellyfin.databinding.ActivityMainBinding
import dev.jdtech.jellyfin.utils.loadDownloadLocation import dev.jdtech.jellyfin.utils.loadDownloadLocation
import dev.jdtech.jellyfin.viewmodels.MainViewModel import dev.jdtech.jellyfin.viewmodels.MainViewModel
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels()
private var castSession: CastSession? = null
private lateinit var sessionManager: SessionManager
private val sessionManagerListener: SessionManagerListener<CastSession> =
SessionManagerListenerImpl(this)
@Inject @Inject
lateinit var database: ServerDatabaseDao lateinit var database: ServerDatabaseDao
@ -34,6 +45,9 @@ class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController private lateinit var navController: NavController
@Inject
lateinit var jellyfinApi: JellyfinApi
@OptIn(NavigationUiSaveStateControl::class) @OptIn(NavigationUiSaveStateControl::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -42,6 +56,7 @@ class MainActivity : AppCompatActivity() {
setTheme(R.style.Theme_FindroidAMOLED) setTheme(R.style.Theme_FindroidAMOLED)
} }
sessionManager = CastContext.getSharedInstance(this).sessionManager
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -91,6 +106,19 @@ class MainActivity : AppCompatActivity() {
loadDownloadLocation(applicationContext) loadDownloadLocation(applicationContext)
} }
override fun onResume() {
super.onResume()
castSession = sessionManager.currentCastSession
sessionManager.addSessionManagerListener(sessionManagerListener, CastSession::class.java)
}
override fun onPause() {
super.onPause()
sessionManager.removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
castSession = null
}
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() return navController.navigateUp()
} }
@ -118,4 +146,45 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
companion object {
private class SessionManagerListenerImpl(private val mainActivity: MainActivity) :
SessionManagerListener<CastSession> {
override fun onSessionStarted(session: CastSession, sessionId: String) {
mainActivity.invalidateOptionsMenu()
val thing =
"{\"options\":{},\"command\":\"Identify\",\"userId\":\"${mainActivity.jellyfinApi.userId}\",\"deviceId\":\"${mainActivity.jellyfinApi.api.deviceInfo.id}\",\"accessToken\":\"${mainActivity.jellyfinApi.api.accessToken}\",\"serverAddress\":\"${mainActivity.jellyfinApi.api.baseUrl}\",\"serverId\":\"\",\"serverVersion\":\"\",\"receiverName\":\"\"}"
session.sendMessage("urn:x-cast:com.connectsdk", thing)
session.setMessageReceivedCallbacks(
"urn:x-cast:com.connectsdk"
) { _, _, message -> Timber.i(message) }
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
mainActivity.invalidateOptionsMenu()
}
override fun onSessionEnded(session: CastSession, error: Int) {
// finish()
}
override fun onSessionEnding(p0: CastSession) {
}
override fun onSessionResumeFailed(p0: CastSession, p1: Int) {
}
override fun onSessionResuming(p0: CastSession, p1: String) {
}
override fun onSessionStartFailed(p0: CastSession, p1: Int) {
}
override fun onSessionStarting(p0: CastSession) {
}
override fun onSessionSuspended(p0: CastSession, p1: Int) {
}
}
}
} }

View file

@ -0,0 +1,40 @@
package dev.jdtech.jellyfin.chromecast
import android.content.Context
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
class CastOptionsProvider : OptionsProvider {
companion object {
const val CUSTOM_NAMESPACE = "urn:x-cast:com.connectsdk"
}
override fun getCastOptions(context: Context): CastOptions {
val supportedNamespaces: MutableList<String> = ArrayList()
supportedNamespaces.add(CUSTOM_NAMESPACE)
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
.build()
return CastOptions.Builder()
// .setReceiverApplicationId("F007D354")
.setReceiverApplicationId("D991CC1E")
// .setSupportedNamespaces(supportedNamespaces)
.setCastMediaOptions(mediaOptions)
.build()
}
override fun getAdditionalSessionProviders(p0: Context): MutableList<SessionProvider>? {
return null
}
}

View file

@ -0,0 +1,15 @@
package dev.jdtech.jellyfin.chromecast
import android.view.Menu
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import dev.jdtech.jellyfin.R
class ExpandedControlsActivity : ExpandedControllerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.expanded_controller, menu)
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
return true
}
}

View file

@ -20,6 +20,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.gms.cast.framework.CastButtonFactory
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
@ -65,6 +66,11 @@ class HomeFragment : Fragment() {
object : MenuProvider { object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.home_menu, menu) menuInflater.inflate(R.menu.home_menu, menu)
CastButtonFactory.setUpMediaRouteButton(
requireContext(),
menu,
R.id.media_route_menu_item
)
val settings = menu.findItem(R.id.action_settings) val settings = menu.findItem(R.id.action_settings)
val search = menu.findItem(R.id.action_search) val search = menu.findItem(R.id.action_search)

View file

@ -18,6 +18,8 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearSnapHelper
import com.google.android.gms.cast.framework.CastButtonFactory
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
@ -64,6 +66,11 @@ class LibraryFragment : Fragment() {
object : MenuProvider { object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.library_menu, menu) menuInflater.inflate(R.menu.library_menu, menu)
CastButtonFactory.setUpMediaRouteButton(
context!!,
menu,
R.id.media_route_menu_item
)
} }
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {

View file

@ -18,6 +18,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.gms.cast.framework.CastButtonFactory
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.CollectionListAdapter import dev.jdtech.jellyfin.adapters.CollectionListAdapter
@ -85,6 +86,11 @@ class MediaFragment : Fragment() {
object : MenuProvider { object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.media_menu, menu) menuInflater.inflate(R.menu.media_menu, menu)
CastButtonFactory.setUpMediaRouteButton(
requireContext(),
menu,
R.id.media_route_menu_item
)
val search = menu.findItem(R.id.action_search) val search = menu.findItem(R.id.action_search)
val searchView = search.actionView as SearchView val searchView = search.actionView as SearchView

View file

@ -4,6 +4,19 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<fragment
android:id="@+id/cast_mini_controller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
app:castShowImageThumbnail="true"
app:layout_constraintBottom_toTopOf="@+id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view" android:id="@+id/nav_view"
android:layout_width="0dp" android:layout_width="0dp"
@ -21,7 +34,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:defaultNavHost="true" app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view" app:layout_constraintBottom_toTopOf="@id/cast_mini_controller"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/main_toolbar_layout" app:layout_constraintTop_toBottomOf="@id/main_toolbar_layout"

View file

@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>

View file

@ -10,7 +10,11 @@
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView" app:showAsAction="always|collapseActionView"
tools:ignore="AlwaysShowAction" /> tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:icon="@drawable/ic_settings" android:icon="@drawable/ic_settings"

View file

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
<!--Icon is currently not used but maybe in the future?--> <!--Icon is currently not used but maybe in the future?-->
<item <item
android:id="@+id/action_sort_by" android:id="@+id/action_sort_by"

View file

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
android:icon="@drawable/ic_search" android:icon="@drawable/ic_search"

View file

@ -163,4 +163,5 @@
<string name="dolby_logo_desc">Dolby Logo</string> <string name="dolby_logo_desc">Dolby Logo</string>
<string name="extra_info">Display Extra Info</string> <string name="extra_info">Display Extra Info</string>
<string name="extra_info_summary">Displays detailed information about Audio, Video and Subtitles</string> <string name="extra_info_summary">Displays detailed information about Audio, Video and Subtitles</string>
<string name="media_route_menu_title">Cast Thing</string>
</resources> </resources>

View file

@ -46,6 +46,18 @@
<item name="dialogCornerRadius">28dp</item> <item name="dialogCornerRadius">28dp</item>
<item name="preferenceTheme">@style/ThemeOverlay.Findroid.Preference</item> <item name="preferenceTheme">@style/ThemeOverlay.Findroid.Preference</item>
<item name="dolbyColor">#000</item> <item name="dolbyColor">#000</item>
<!-- Cast SDK -->
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
</style>
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castProgressBarColor">?attr/colorPrimary</item>
</style>
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castSeekBarProgressAndThumbColor">?attr/colorPrimary</item>
</style> </style>
<string-array name="themes"> <string-array name="themes">

View file

@ -24,6 +24,8 @@ ktlint = "11.1.0"
libmpv = "0.1.1" libmpv = "0.1.1"
material = "1.8.0" material = "1.8.0"
timber = "5.0.1" timber = "5.0.1"
androidx-mediarouter = "1.3.1"
play-services-cast-framework = "21.2.0"
[libraries] [libraries]
aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" } aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" }
@ -57,6 +59,8 @@ hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hil
jellyfin-core = { module = "org.jellyfin.sdk:jellyfin-core", version.ref = "jellyfin" } jellyfin-core = { module = "org.jellyfin.sdk:jellyfin-core", version.ref = "jellyfin" }
libmpv = { module = "dev.jdtech.mpv:libmpv", version.ref = "libmpv" } libmpv = { module = "dev.jdtech.mpv:libmpv", version.ref = "libmpv" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }
mediarouter = { module = "androidx.mediarouter:mediarouter", version.ref = "androidx-mediarouter" }
playServicesCastFramework = { module = "com.google.android.gms:play-services-cast-framework", version.ref = "play-services-cast-framework"}
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }

View file

@ -57,4 +57,5 @@ dependencies {
implementation(libs.libmpv) implementation(libs.libmpv)
implementation(libs.material) implementation(libs.material)
implementation(libs.timber) implementation(libs.timber)
implementation(libs.playServicesCastFramework)
} }

View file

@ -1,12 +1,16 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import com.google.android.gms.cast.framework.CastContext
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.ExternalSubtitle import dev.jdtech.jellyfin.models.ExternalSubtitle
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
@ -18,6 +22,7 @@ import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemFields
@ -30,7 +35,9 @@ import timber.log.Timber
class PlayerViewModel @Inject internal constructor( class PlayerViewModel @Inject internal constructor(
private val application: Application, private val application: Application,
private val repository: JellyfinRepository, private val repository: JellyfinRepository,
private val downloadDatabase: DownloadDatabaseDao private val downloadDatabase: DownloadDatabaseDao,
private val jellyfinApi: JellyfinApi,
@ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
private val playerItems = MutableSharedFlow<PlayerItemState>( private val playerItems = MutableSharedFlow<PlayerItemState>(
@ -61,6 +68,17 @@ class PlayerViewModel @Inject internal constructor(
} }
viewModelScope.launch { viewModelScope.launch {
val session = CastContext.getSharedInstance(context).sessionManager.currentCastSession
if (session != null) {
val thing =
"{\"options\":{\"ids\":[\"${item.id}\"],\"startPositionTicks\":${
item.userData?.playbackPositionTicks ?: 0
},\"serverId\":\"\",\"fullscreen\":true,\"items\":[{\"Id\":\"${item.id}\",\"ServerId\":\"\",\"Name\":\"${item.name}\",\"Type\":\"${item.type}\",\"MediaType\":\"${item.mediaType}\",\"IsFolder\":false}]},\"command\":\"PlayNow\",\"userId\":\"${jellyfinApi.userId}\",\"deviceId\":\"${jellyfinApi.api.deviceInfo.id}\",\"accessToken\":\"${jellyfinApi.api.accessToken}\",\"serverAddress\":\"${jellyfinApi.api.baseUrl}\",\"serverId\":\"\",\"serverVersion\":\"\",\"receiverName\":\"Living Room TV\",\"subtitleAppearance\":{\"verticalPosition\":-3},\"subtitleBurnIn\":\"\"}"
session.sendMessage("urn:x-cast:com.connectsdk", thing)
return@launch
}
val playbackPosition = item.userData?.playbackPositionTicks?.div(10000) ?: 0 val playbackPosition = item.userData?.playbackPositionTicks?.div(10000) ?: 0
val items = try { val items = try {
@ -69,7 +87,6 @@ class PlayerViewModel @Inject internal constructor(
Timber.d(e) Timber.d(e)
PlayerItemError(e) PlayerItemError(e)
} }
playerItems.tryEmit(items) playerItems.tryEmit(items)
} }
} }