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
2 changes: 1 addition & 1 deletion .github/workflows/android_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Android Build

on:
push:
branches: [ main ]
branches: [ main, fix-dv-exynos ]
pull_request:
concurrency:
group: build-${{ github.ref }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fun MediumStateEntity.toVideoState(): VideoState {
playbackSpeed = playbackSpeed,
externalSubs = UriListConverter.fromStringToList(externalSubs),
videoScale = videoScale,
videoScaleY = videoScaleY,
subtitleDelayMilliseconds = subtitleDelayMilliseconds,
subtitleSpeed = subtitleSpeed,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ data class VideoState(
val playbackSpeed: Float?,
val externalSubs: List<Uri>,
val videoScale: Float,
val videoScaleY: Float,
val subtitleDelayMilliseconds: Long,
val subtitleSpeed: Float,
)
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@ class LocalMediaRepository @Inject constructor(
)
}

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

mediumStateDao.upsert(
mediumState = stateEntity.copy(
videoScale = zoom,
videoScale = zoomX,
videoScaleY = zoomY,
lastPlayedTime = System.currentTimeMillis(),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,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 updateMediumZoom(uri: String, zoom: Float)
suspend fun updateMediumZoom(uri: String, zoomX: Float, zoomY: Float)
suspend fun addExternalSubtitleToMedium(uri: String, subtitleUri: Uri)
suspend fun updateSubtitleDelay(uri: String, delay: Long)
suspend fun updateSubtitleSpeed(uri: String, speed: Float)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class FakeMediaRepository : MediaRepository {
override suspend fun updateMediumSubtitleTrack(uri: String, subtitleTrackIndex: Int) {
}

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

override suspend fun addExternalSubtitleToMedium(uri: String, subtitleUri: Uri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ object DatabaseModule {
MediaDatabase.MIGRATION_4_5,
MediaDatabase.MIGRATION_5_6,
MediaDatabase.MIGRATION_6_7,
MediaDatabase.MIGRATION_7_8,
)
fallbackToDestructiveMigration(false)
}.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import dev.anilbeesetti.nextplayer.core.database.entities.NetworkConnectionEntit
HiddenVideoEntity::class,
NetworkConnectionEntity::class,
],
version = 7,
version = 8,
exportSchema = true,
)
abstract class MediaDatabase : RoomDatabase() {
Expand Down Expand Up @@ -236,5 +236,11 @@ abstract class MediaDatabase : RoomDatabase() {
)
}
}

val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE `media_state` ADD COLUMN `video_scale_y` REAL NOT NULL DEFAULT 1")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ data class MediumStateEntity(
val externalSubs: String = "",
@ColumnInfo(name = "video_scale")
val videoScale: Float = 1f,
@ColumnInfo(name = "video_scale_y")
val videoScaleY: Float = 1f,
@ColumnInfo(name = "subtitle_delay")
val subtitleDelayMilliseconds: Long = 0,
@ColumnInfo(name = "subtitle_speed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ data class PlayerPreferences(

// Decoder Preferences
val decoderPriority: DecoderPriority = DecoderPriority.PREFER_DEVICE,
val dv7Fallback: Boolean = false,
) {

companion object {
Expand Down
2 changes: 2 additions & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,6 @@
<item quantity="other">Moved %1$d videos, but couldn\'t remove the originals</item>
</plurals>
<string name="cannot_move_to_same_folder">Can\'t move to the same folder</string>
<string name="dv7_fallback">DV Profile 7 Fallback</string>
<string name="dv7_fallback_description">Fixes black screen issue for Dolby Vision Profile 7 on Exynos devices</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ fun MediaPlayerScreen(
middleView = {
when {
seekGestureState.seekAmount != null -> InfoView(info = "${seekGestureState.seekAmountFormatted}\n[${seekGestureState.seekToPositionFormated}]")
videoZoomAndContentScaleState.isZooming -> InfoView(info = "${(videoZoomAndContentScaleState.zoom * 100).toInt()}%")
videoZoomAndContentScaleState.isZooming -> InfoView(info = "${(videoZoomAndContentScaleState.zoomX * 100).toInt()}% x ${(videoZoomAndContentScaleState.zoomY * 100).toInt()}%")
videoZoomAndContentScaleState.showContentScaleIndicator -> InfoView(info = stringResource(videoZoomAndContentScaleState.videoContentScale.nameRes()))
controlsVisibilityState.controlsVisible -> ControlsMiddleView(
player = player,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ fun PlayerContentFrame(
pictureInPictureState.setVideoViewRect(rect)
}
.graphicsLayer {
scaleX = videoZoomAndContentScaleState.zoom
scaleY = videoZoomAndContentScaleState.zoom
scaleX = videoZoomAndContentScaleState.zoomX
scaleY = videoZoomAndContentScaleState.zoomY
translationX = videoZoomAndContentScaleState.offset.x
translationY = videoZoomAndContentScaleState.offset.y
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ class PlayerViewModel @Inject constructor(
return getSortedPlaylistUseCase.invoke(uri)
}

fun updateVideoZoom(uri: String, zoom: Float) {
fun updateVideoZoom(uri: String, zoomX: Float, zoomY: Float) {
viewModelScope.launch {
mediaRepository.updateMediumZoom(uri, zoom)
mediaRepository.updateMediumZoom(uri, zoomX, zoomY)
}
}

Expand Down Expand Up @@ -78,7 +78,7 @@ class PlayerViewModel @Inject constructor(
updateVideoContentScale(event.contentScale)
}
is VideoZoomEvent.ZoomChanged -> {
updateVideoZoom(event.mediaItem.mediaId, event.zoom)
updateVideoZoom(event.mediaItem.mediaId, event.zoomX, event.zoomY)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ private const val MEDIA_METADATA_PLAYBACK_SPEED_KEY = "media_metadata_playback_s
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_ZOOM_KEY = "media_metadata_video_zoom"
private const val MEDIA_METADATA_VIDEO_ZOOM_X_KEY = "media_metadata_video_zoom_x"
private const val MEDIA_METADATA_VIDEO_ZOOM_Y_KEY = "media_metadata_video_zoom_y"
private const val MEDIA_METADATA_SUBTITLE_DELAY_KEY = "media_metadata_subtitle_delay"
private const val MEDIA_METADATA_SUBTITLE_SPEED_KEY = "media_metadata_subtitle_speed"

private fun Bundle.setExtras(
positionMs: Long?,
videoScale: Float?,
videoScaleX: Float?,
videoScaleY: Float?,
playbackSpeed: Float?,
audioTrackIndex: Int?,
subtitleTrackIndex: Int?,
Expand All @@ -23,6 +27,8 @@ private fun Bundle.setExtras(
) = apply {
positionMs?.let { putLong(MEDIA_METADATA_POSITION_KEY, it) }
videoScale?.let { putFloat(MEDIA_METADATA_VIDEO_ZOOM_KEY, it) }
videoScaleX?.let { putFloat(MEDIA_METADATA_VIDEO_ZOOM_X_KEY, it) }
videoScaleY?.let { putFloat(MEDIA_METADATA_VIDEO_ZOOM_Y_KEY, it) }
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) }
Expand All @@ -33,6 +39,8 @@ private fun Bundle.setExtras(
fun MediaMetadata.Builder.setExtras(
positionMs: Long? = null,
videoScale: Float? = null,
videoScaleX: Float? = null,
videoScaleY: Float? = null,
playbackSpeed: Float? = null,
audioTrackIndex: Int? = null,
subtitleTrackIndex: Int? = null,
Expand All @@ -42,6 +50,8 @@ fun MediaMetadata.Builder.setExtras(
Bundle().setExtras(
positionMs = positionMs,
videoScale = videoScale,
videoScaleX = videoScaleX,
videoScaleY = videoScaleY,
playbackSpeed = playbackSpeed,
audioTrackIndex = audioTrackIndex,
subtitleTrackIndex = subtitleTrackIndex,
Expand Down Expand Up @@ -80,6 +90,18 @@ val MediaMetadata.videoZoom: Float?
.takeIf { containsKey(MEDIA_METADATA_VIDEO_ZOOM_KEY) }
}

val MediaMetadata.videoZoomX: Float?
get() = extras?.run {
getFloat(MEDIA_METADATA_VIDEO_ZOOM_X_KEY)
.takeIf { containsKey(MEDIA_METADATA_VIDEO_ZOOM_X_KEY) }
} ?: videoZoom

val MediaMetadata.videoZoomY: Float?
get() = extras?.run {
getFloat(MEDIA_METADATA_VIDEO_ZOOM_Y_KEY)
.takeIf { containsKey(MEDIA_METADATA_VIDEO_ZOOM_Y_KEY) }
} ?: videoZoom

val MediaMetadata.subtitleDelayMilliseconds: Long?
get() = extras?.run {
getLong(MEDIA_METADATA_SUBTITLE_DELAY_KEY)
Expand All @@ -96,6 +118,8 @@ fun MediaItem.copy(
positionMs: Long? = this.mediaMetadata.positionMs,
durationMs: Long? = this.mediaMetadata.durationMs,
videoZoom: Float? = this.mediaMetadata.videoZoom,
videoZoomX: Float? = this.mediaMetadata.videoZoomX,
videoZoomY: Float? = this.mediaMetadata.videoZoomY,
playbackSpeed: Float? = this.mediaMetadata.playbackSpeed,
audioTrackIndex: Int? = this.mediaMetadata.audioTrackIndex,
subtitleTrackIndex: Int? = this.mediaMetadata.subtitleTrackIndex,
Expand All @@ -108,6 +132,8 @@ fun MediaItem.copy(
Bundle(mediaMetadata.extras).setExtras(
positionMs = positionMs,
videoScale = videoZoom,
videoScaleX = videoZoomX,
videoScaleY = videoZoomY,
playbackSpeed = playbackSpeed,
audioTrackIndex = audioTrackIndex,
subtitleTrackIndex = subtitleTrackIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,50 @@ import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.positionChanged
import kotlin.math.abs

fun PointerEvent.calculateZoomXY(): Offset {
val pointers = changes.filter { it.pressed || it.previousPressed }
if (pointers.size < 2) return Offset(1f, 1f)

val p1 = pointers[0]
val p2 = pointers[1]

val currentDistanceX = kotlin.math.abs(p1.position.x - p2.position.x)
val currentDistanceY = kotlin.math.abs(p1.position.y - p2.position.y)

val previousDistanceX = kotlin.math.abs(p1.previousPosition.x - p2.previousPosition.x)
val previousDistanceY = kotlin.math.abs(p1.previousPosition.y - p2.previousPosition.y)

val previousDistance = kotlin.math.hypot(
p1.previousPosition.x - p2.previousPosition.x,
p1.previousPosition.y - p2.previousPosition.y
)

if (previousDistance < 10f) return Offset(1f, 1f)

var deltaX = currentDistanceX - previousDistanceX
var deltaY = currentDistanceY - previousDistanceY

// Snap based on finger placement
if (previousDistanceX >= previousDistanceY) {
deltaY = 0f
} else {
deltaX = 0f
}

val zoomX = 1f + (deltaX / previousDistance)
val zoomY = 1f + (deltaY / previousDistance)

return Offset(zoomX, zoomY)
}

/**
* Detects custom transform gestures including pan, zoom, and rotation gestures.
* This function allows customization of gesture handling and locks gestures to pan and zoom if specified.
Expand All @@ -41,14 +78,14 @@ suspend fun PointerInputScope.detectCustomTransformGestures(
onGesture: (
centroid: Offset,
pan: Offset,
zoom: Float,
zoom: Offset,
rotation: Float,
) -> Unit,
onGestureEnd: (PointerInputChange) -> Unit = {},
) {
awaitEachGesture {
var rotation = 0f
var zoom = 1f
var zoom = Offset(1f, 1f)
var pan = Offset.Zero
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
Expand Down Expand Up @@ -92,23 +129,25 @@ suspend fun PointerInputScope.detectCustomTransformGestures(
pointerId = pointerInputChange.id
pointer = pointerInputChange

val zoomChange = event.calculateZoom()
val zoomChange = event.calculateZoomXY()
val rotationChange = event.calculateRotation()
val panChange = event.calculatePan()

if (!pastTouchSlop) {
zoom *= zoomChange
zoom = Offset(zoom.x * zoomChange.x, zoom.y * zoomChange.y)
rotation += rotationChange
pan += panChange

val centroidSize = event.calculateCentroidSize(useCurrent = false)
val zoomMotion = abs(1 - zoom) * centroidSize
val zoomMotionX = abs(1 - zoom.x) * centroidSize
val zoomMotionY = abs(1 - zoom.y) * centroidSize
val rotationMotion = abs(
rotation * kotlin.math.PI.toFloat() * centroidSize / 180f,
)
val panMotion = pan.getDistance()

if (zoomMotion > touchSlop ||
if (zoomMotionX > touchSlop ||
zoomMotionY > touchSlop ||
rotationMotion > touchSlop ||
panMotion > touchSlop
) {
Expand All @@ -121,7 +160,7 @@ suspend fun PointerInputScope.detectCustomTransformGestures(
val centroid = event.calculateCentroid(useCurrent = false)
val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
if (effectiveRotation != 0f ||
zoomChange != 1f ||
zoomChange != Offset(1f, 1f) ||
panChange != Offset.Zero
) {
onGesture(
Expand Down
Loading