Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fun MediumStateEntity.toVideoState(): VideoState {
position = playbackPosition.takeIf { it != 0L },
audioTrackIndex = audioTrackIndex,
subtitleTrackIndex = subtitleTrackIndex,
videoTrackIndex = videoTrackIndex,
playbackSpeed = playbackSpeed,
externalSubs = UriListConverter.fromStringToList(externalSubs),
videoScale = videoScale,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ data class VideoState(
val position: Long?,
val audioTrackIndex: Int?,
val subtitleTrackIndex: Int?,
val videoTrackIndex: Int?,
val playbackSpeed: Float?,
val externalSubs: List<Uri>,
val videoScale: Float,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ class LocalMediaRepository @Inject constructor(
)
}

override suspend fun updateMediumVideoTrack(uri: String, videoTrackIndex: Int) {
val stateEntity = mediumStateDao.get(uri) ?: MediumStateEntity(uriString = uri)

mediumStateDao.upsert(
mediumState = stateEntity.copy(
videoTrackIndex = videoTrackIndex,
lastPlayedTime = System.currentTimeMillis(),
),
)
}

override suspend fun updateMediumZoom(uri: String, zoom: Float) {
val stateEntity = mediumStateDao.get(uri) ?: MediumStateEntity(uriString = uri)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface MediaRepository {
suspend fun updateMediumPlaybackSpeed(uri: String, playbackSpeed: Float)
suspend fun updateMediumAudioTrack(uri: String, audioTrackIndex: Int)
suspend fun updateMediumSubtitleTrack(uri: String, subtitleTrackIndex: Int)
suspend fun updateMediumVideoTrack(uri: String, videoTrackIndex: Int)
suspend fun updateMediumZoom(uri: String, zoom: Float)
suspend fun addExternalSubtitleToMedium(uri: String, subtitleUri: Uri)
suspend fun updateSubtitleDelay(uri: String, delay: Long)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class FakeMediaRepository : MediaRepository {
override suspend fun updateMediumSubtitleTrack(uri: String, subtitleTrackIndex: Int) {
}

override suspend fun updateMediumVideoTrack(uri: String, videoTrackIndex: Int) {
}

override suspend fun updateMediumZoom(uri: String, zoom: Float) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "81bbaaf0c1259ab32ca50b413f461fdd",
"identityHash": "eca1a98d315706294085d944ae505a6e",
"entities": [
{
"tableName": "directories",
Expand Down Expand Up @@ -143,7 +143,7 @@
},
{
"tableName": "media_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uri` TEXT NOT NULL, `playback_position` INTEGER NOT NULL, `audio_track_index` INTEGER, `subtitle_track_index` INTEGER, `playback_speed` REAL, `last_played_time` INTEGER, `external_subs` TEXT NOT NULL, `video_scale` REAL NOT NULL, `subtitle_delay` INTEGER NOT NULL, `subtitle_speed` REAL NOT NULL, PRIMARY KEY(`uri`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uri` TEXT NOT NULL, `playback_position` INTEGER NOT NULL, `audio_track_index` INTEGER, `subtitle_track_index` INTEGER, `video_track_index` INTEGER, `playback_speed` REAL, `last_played_time` INTEGER, `external_subs` TEXT NOT NULL, `video_scale` REAL NOT NULL, `subtitle_delay` INTEGER NOT NULL, `subtitle_speed` REAL NOT NULL, PRIMARY KEY(`uri`))",
"fields": [
{
"fieldPath": "uriString",
Expand All @@ -167,6 +167,11 @@
"columnName": "subtitle_track_index",
"affinity": "INTEGER"
},
{
"fieldPath": "videoTrackIndex",
"columnName": "video_track_index",
"affinity": "INTEGER"
},
{
"fieldPath": "playbackSpeed",
"columnName": "playback_speed",
Expand Down Expand Up @@ -455,7 +460,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '81bbaaf0c1259ab32ca50b413f461fdd')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eca1a98d315706294085d944ae505a6e')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ object DatabaseModule {
klass = MediaDatabase::class.java,
name = MediaDatabase.DATABASE_NAME,
).apply {
addMigrations(MediaDatabase.MIGRATION_1_2, MediaDatabase.MIGRATION_2_3, MediaDatabase.MIGRATION_3_4)
addMigrations(
MediaDatabase.MIGRATION_1_2,
MediaDatabase.MIGRATION_2_3,
MediaDatabase.MIGRATION_3_4,
)
fallbackToDestructiveMigration(false)
}.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ abstract class MediaDatabase : RoomDatabase() {

val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `media_state` ADD COLUMN `video_track_index` INTEGER")
db.execSQL("ALTER TABLE `media_state` ADD COLUMN `subtitle_delay` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE `media_state` ADD COLUMN `subtitle_speed` REAL NOT NULL DEFAULT 1")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ data class MediumStateEntity(
val audioTrackIndex: Int? = null,
@ColumnInfo(name = "subtitle_track_index")
val subtitleTrackIndex: Int? = null,
@ColumnInfo(name = "video_track_index")
val videoTrackIndex: Int? = null,
@ColumnInfo(name = "playback_speed")
val playbackSpeed: Float? = null,
@ColumnInfo(name = "last_played_time")
Expand Down
9 changes: 9 additions & 0 deletions core/ui/src/main/res/drawable/ic_video_track.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M17,10.5V6c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v12c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-4.5l4,4v-11l-4,4z" />
</vector>
1 change: 1 addition & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<string name="seek_gesture">Seek gesture</string>
<string name="seek_gesture_description">Swipe horizontally to seek forwards or backwards</string>
<string name="select_audio_track">Select audio track</string>
<string name="select_video_track">Select video track</string>
<string name="select_subtitle_track">Select subtitle track</string>
<string name="settings">Settings</string>
<string name="size">Size</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.media3.common.C
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import dev.anilbeesetti.nextplayer.core.model.ControlButtonsPosition
Expand All @@ -71,6 +72,7 @@ import dev.anilbeesetti.nextplayer.feature.player.state.rememberPictureInPicture
import dev.anilbeesetti.nextplayer.feature.player.state.rememberRotationState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberSeekGestureState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberTapGestureState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberTracksState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberVideoZoomAndContentScaleState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberVolumeAndBrightnessGestureState
import dev.anilbeesetti.nextplayer.feature.player.state.rememberVolumeState
Expand Down Expand Up @@ -147,6 +149,8 @@ fun MediaPlayerScreen(
screenOrientation = playerPreferences.playerScreenOrientation,
)
val errorState = rememberErrorState(player = player)
val videoTracksState = rememberTracksState(player, C.TRACK_TYPE_VIDEO)
val hasMultipleVideoTracks = videoTracksState.tracks.size > 1

LaunchedEffect(pictureInPictureState.isInPictureInPictureMode) {
if (pictureInPictureState.isInPictureInPictureMode) {
Expand Down Expand Up @@ -174,6 +178,12 @@ fun MediaPlayerScreen(

var overlayView by remember { mutableStateOf<OverlayView?>(null) }

LaunchedEffect(hasMultipleVideoTracks, overlayView) {
if (!hasMultipleVideoTracks && overlayView == OverlayView.VIDEO_SELECTOR) {
overlayView = null
}
}

CompositionLocalProvider(LocalControlsVisibilityState provides controlsVisibilityState) {
Box {
Box(
Expand Down Expand Up @@ -268,6 +278,11 @@ fun MediaPlayerScreen(
) {
ControlsTopView(
title = metadataState.title ?: "",
showVideoTrackButton = hasMultipleVideoTracks,
onVideoTrackClick = {
controlsVisibilityState.hideControls()
overlayView = OverlayView.VIDEO_SELECTOR
},
onAudioClick = {
controlsVisibilityState.hideControls()
overlayView = OverlayView.AUDIO_SELECTOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ private const val MEDIA_METADATA_POSITION_KEY = "media_metadata_position"
private const val MEDIA_METADATA_PLAYBACK_SPEED_KEY = "media_metadata_playback_speed"
private const val MEDIA_METADATA_AUDIO_TRACK_INDEX_KEY = "audio_track_index"
private const val MEDIA_METADATA_SUBTITLE_TRACK_INDEX_KEY = "subtitle_track_index"
private const val MEDIA_METADATA_VIDEO_TRACK_INDEX_KEY = "video_track_index"
private const val MEDIA_METADATA_VIDEO_ZOOM_KEY = "media_metadata_video_zoom"
private const val MEDIA_METADATA_SUBTITLE_DELAY_KEY = "media_metadata_subtitle_delay"
private const val MEDIA_METADATA_SUBTITLE_SPEED_KEY = "media_metadata_subtitle_speed"
Expand All @@ -18,6 +19,7 @@ private fun Bundle.setExtras(
playbackSpeed: Float?,
audioTrackIndex: Int?,
subtitleTrackIndex: Int?,
videoTrackIndex: Int?,
subtitleDelayMilliseconds: Long? = null,
subtitleSpeed: Float? = null,
) = apply {
Expand All @@ -26,6 +28,7 @@ private fun Bundle.setExtras(
playbackSpeed?.let { putFloat(MEDIA_METADATA_PLAYBACK_SPEED_KEY, it) }
audioTrackIndex?.let { putInt(MEDIA_METADATA_AUDIO_TRACK_INDEX_KEY, it) }
subtitleTrackIndex?.let { putInt(MEDIA_METADATA_SUBTITLE_TRACK_INDEX_KEY, it) }
videoTrackIndex?.let { putInt(MEDIA_METADATA_VIDEO_TRACK_INDEX_KEY, it) }
subtitleDelayMilliseconds?.let { putLong(MEDIA_METADATA_SUBTITLE_DELAY_KEY, it) }
subtitleSpeed?.let { putFloat(MEDIA_METADATA_SUBTITLE_SPEED_KEY, it) }
}
Expand All @@ -38,7 +41,8 @@ fun MediaMetadata.Builder.setExtras(
subtitleTrackIndex: Int? = null,
subtitleDelayMilliseconds: Long? = null,
subtitleSpeed: Float? = null,
): MediaMetadata.Builder = setExtras(
videoTrackIndex: Int? = null,
): MediaMetadata.Builder = setExtras(
Bundle().setExtras(
positionMs = positionMs,
videoScale = videoScale,
Expand All @@ -47,6 +51,7 @@ fun MediaMetadata.Builder.setExtras(
subtitleTrackIndex = subtitleTrackIndex,
subtitleDelayMilliseconds = subtitleDelayMilliseconds,
subtitleSpeed = subtitleSpeed,
videoTrackIndex = videoTrackIndex,
),
)

Expand Down Expand Up @@ -74,6 +79,12 @@ val MediaMetadata.subtitleTrackIndex: Int?
.takeIf { containsKey(MEDIA_METADATA_SUBTITLE_TRACK_INDEX_KEY) }
}

val MediaMetadata.videoTrackIndex: Int?
get() = extras?.run {
getInt(MEDIA_METADATA_VIDEO_TRACK_INDEX_KEY)
.takeIf { containsKey(MEDIA_METADATA_VIDEO_TRACK_INDEX_KEY) }
}

val MediaMetadata.videoZoom: Float?
get() = extras?.run {
getFloat(MEDIA_METADATA_VIDEO_ZOOM_KEY)
Expand All @@ -99,6 +110,7 @@ fun MediaItem.copy(
playbackSpeed: Float? = this.mediaMetadata.playbackSpeed,
audioTrackIndex: Int? = this.mediaMetadata.audioTrackIndex,
subtitleTrackIndex: Int? = this.mediaMetadata.subtitleTrackIndex,
videoTrackIndex: Int? = this.mediaMetadata.videoTrackIndex,
subtitleDelayMilliseconds: Long? = this.mediaMetadata.subtitleDelayMilliseconds,
subtitleSpeed: Float? = this.mediaMetadata.subtitleSpeed,
): MediaItem = buildUpon().setMediaMetadata(
Expand All @@ -111,6 +123,7 @@ fun MediaItem.copy(
playbackSpeed = playbackSpeed,
audioTrackIndex = audioTrackIndex,
subtitleTrackIndex = subtitleTrackIndex,
videoTrackIndex = videoTrackIndex,
subtitleDelayMilliseconds = subtitleDelayMilliseconds,
subtitleSpeed = subtitleSpeed,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@ fun Player.switchTrack(trackType: @C.TrackType Int, trackIndex: Int) {
val trackTypeText = when (trackType) {
C.TRACK_TYPE_AUDIO -> "audio"
C.TRACK_TYPE_TEXT -> "subtitle"
C.TRACK_TYPE_VIDEO -> "video"
else -> throw IllegalArgumentException("Invalid track type: $trackType")
}

if (trackIndex < 0) {
if (trackType == C.TRACK_TYPE_VIDEO) {
Logger.logDebug("Player", "Ignoring disable request for video track")
return
}
Logger.logDebug("Player", "Disabling $trackTypeText")
trackSelectionParameters = trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(trackType, true)
.build()
} else {
val tracks = currentTracks.groups.filter { it.type == trackType }
val tracks = currentTracks.groups.filter { it.type == trackType && it.isSupported }

if (tracks.isEmpty() || trackIndex >= tracks.size) {
Logger.logError("Player", "Operation failed: Invalid track index: $trackIndex")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ fun TrackGroup.getName(trackType: @C.TrackType Int, index: Int): String {
append(label)
}
if (isEmpty()) {
if (trackType == C.TRACK_TYPE_TEXT) {
append("Subtitle Track #${index + 1}")
} else {
append("Audio Track #${index + 1}")
when (trackType) {
C.TRACK_TYPE_TEXT -> append("Subtitle Track #${index + 1}")
C.TRACK_TYPE_VIDEO -> append("Video Track #${index + 1}")
C.TRACK_TYPE_AUDIO -> append("Audio Track #${index + 1}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import dev.anilbeesetti.nextplayer.feature.player.extensions.subtitleSpeed
import dev.anilbeesetti.nextplayer.feature.player.extensions.subtitleTrackIndex
import dev.anilbeesetti.nextplayer.feature.player.extensions.switchTrack
import dev.anilbeesetti.nextplayer.feature.player.extensions.uriToSubtitleConfiguration
import dev.anilbeesetti.nextplayer.feature.player.extensions.videoTrackIndex
import dev.anilbeesetti.nextplayer.feature.player.extensions.videoZoom
import io.github.anilbeesetti.nextlib.media3ext.ffdecoder.NextRenderersFactory
import io.github.anilbeesetti.nextlib.media3ext.renderer.subtitleDelayMilliseconds
Expand Down Expand Up @@ -180,6 +181,9 @@ class PlayerService : MediaSessionService() {
mediaSession?.player?.mediaMetadata?.subtitleTrackIndex?.let {
mediaSession?.player?.switchTrack(C.TRACK_TYPE_TEXT, it)
}
mediaSession?.player?.mediaMetadata?.videoTrackIndex?.let {
mediaSession?.player?.switchTrack(C.TRACK_TYPE_VIDEO, it)
}
}
}

Expand All @@ -190,6 +194,7 @@ class PlayerService : MediaSessionService() {

val audioTrackIndex = player.getManuallySelectedTrackIndex(C.TRACK_TYPE_AUDIO)
val subtitleTrackIndex = player.getManuallySelectedTrackIndex(C.TRACK_TYPE_TEXT)
val videoTrackIndex = player.getManuallySelectedTrackIndex(C.TRACK_TYPE_VIDEO)

if (audioTrackIndex != null) {
serviceScope.launch {
Expand All @@ -209,11 +214,21 @@ class PlayerService : MediaSessionService() {
}
}

if (videoTrackIndex != null) {
serviceScope.launch {
mediaRepository.updateMediumVideoTrack(
uri = currentMediaItem.mediaId,
videoTrackIndex = videoTrackIndex,
)
}
}

player.replaceMediaItem(
player.currentMediaItemIndex,
currentMediaItem.copy(
audioTrackIndex = audioTrackIndex,
subtitleTrackIndex = subtitleTrackIndex,
videoTrackIndex = videoTrackIndex,
),
)
}
Expand Down Expand Up @@ -651,6 +666,7 @@ class PlayerService : MediaSessionService() {
val subtitleTrackIndex = mediaItem.mediaMetadata.subtitleTrackIndex ?: videoState?.subtitleTrackIndex
val subtitleDelay = mediaItem.mediaMetadata.subtitleDelayMilliseconds ?: videoState?.subtitleDelayMilliseconds
val subtitleSpeed = mediaItem.mediaMetadata.subtitleSpeed ?: videoState?.subtitleSpeed
val videoTrackIndex = mediaItem.mediaMetadata.videoTrackIndex ?: videoState?.videoTrackIndex

mediaItem.buildUpon().apply {
setSubtitleConfigurations(existingSubConfigurations + subConfigurations)
Expand All @@ -666,6 +682,7 @@ class PlayerService : MediaSessionService() {
subtitleTrackIndex = subtitleTrackIndex,
subtitleDelayMilliseconds = subtitleDelay,
subtitleSpeed = subtitleSpeed,
videoTrackIndex = videoTrackIndex,
)
}.build(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ fun BoxScope.OverlayShowView(
onDismiss = onDismiss,
)

VideoTrackSelectorView(
show = overlayView == OverlayView.VIDEO_SELECTOR,
player = player,
onDismiss = onDismiss,
)

SubtitleSelectorView(
show = overlayView == OverlayView.SUBTITLE_SELECTOR,
player = player,
Expand Down Expand Up @@ -69,6 +75,7 @@ val Configuration.isPortrait: Boolean

enum class OverlayView {
AUDIO_SELECTOR,
VIDEO_SELECTOR,
SUBTITLE_SELECTOR,
PLAYBACK_SPEED,
VIDEO_CONTENT_SCALE,
Expand Down
Loading