Skip to content

Commit 35de33a

Browse files
committed
[feature] Support player portrait mode
1 parent c4477c3 commit 35de33a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1171
-596
lines changed

app/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ android {
2222
minSdk = 24
2323
targetSdk = 35
2424
versionCode = 26
25-
versionName = "3.1-alpha05"
25+
versionName = "3.1-alpha06"
2626

2727
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2828

app/src/main/AndroidManifest.xml

+1-3
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,7 @@
158158
android:exported="true"
159159
android:label="@string/play_activity_label"
160160
android:launchMode="singleTask"
161-
android:screenOrientation="userLandscape"
162-
android:supportsPictureInPicture="true"
163-
android:theme="@style/Theme.PodAura.Player">
161+
android:supportsPictureInPicture="true">
164162
<intent-filter>
165163
<action android:name="android.intent.action.VIEW" />
166164

app/src/main/java/com/skyd/anivu/ext/ActivityExt.kt

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.skyd.anivu.ext
22

3+
import android.annotation.SuppressLint
34
import android.app.Activity
5+
import android.content.pm.ActivityInfo
46
import android.provider.Settings
57

68
/**
@@ -11,4 +13,13 @@ fun Activity.getScreenBrightness(): Int? = try {
1113
} catch (e: Settings.SettingNotFoundException) {
1214
e.printStackTrace()
1315
null
16+
}
17+
18+
fun Activity.landOrientation() {
19+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
20+
}
21+
22+
@SuppressLint("SourceLockedOrientationActivity")
23+
fun Activity.portOrientation() {
24+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
1425
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.skyd.anivu.ext
2+
3+
import android.content.res.Configuration
4+
5+
6+
val Configuration.screenIsLand: Boolean
7+
get() = orientation == Configuration.ORIENTATION_LANDSCAPE

app/src/main/java/com/skyd/anivu/ext/ContextExt.kt

-3
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ val Context.tryWindow: Window?
5151
return null
5252
}
5353

54-
val Context.screenIsLand: Boolean
55-
get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
56-
5754
fun Context.getAppVersionName(): String {
5855
var appVersionName = ""
5956
try {

app/src/main/java/com/skyd/anivu/model/db/dao/MediaPlayHistoryDao.kt

+1-6
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ interface MediaPlayHistoryDao {
2929
suspend fun deleteAllMediaPlayHistory(): Int
3030

3131
@Transaction
32-
@Query(
33-
"""
34-
SELECT * FROM $MEDIA_PLAY_HISTORY_TABLE_NAME
35-
WHERE ${MediaPlayHistoryBean.PATH_COLUMN} = :path
36-
"""
37-
)
32+
@Query("SELECT * FROM $MEDIA_PLAY_HISTORY_TABLE_NAME WHERE ${MediaPlayHistoryBean.PATH_COLUMN} = :path")
3833
fun getMediaPlayHistory(path: String): MediaPlayHistoryBean?
3934

4035
@Transaction

app/src/main/java/com/skyd/anivu/ui/activity/player/PlayActivity.kt

+23-9
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,24 @@ import androidx.compose.runtime.mutableStateOf
2121
import androidx.compose.runtime.setValue
2222
import androidx.core.content.ContextCompat
2323
import androidx.core.util.Consumer
24+
import androidx.lifecycle.Lifecycle
2425
import androidx.lifecycle.compose.collectAsStateWithLifecycle
26+
import androidx.lifecycle.flowWithLifecycle
27+
import androidx.lifecycle.lifecycleScope
2528
import com.skyd.anivu.R
2629
import com.skyd.anivu.base.BaseComposeActivity
2730
import com.skyd.anivu.ext.dataStore
2831
import com.skyd.anivu.ext.getOrDefault
2932
import com.skyd.anivu.ext.savePictureToMediaStore
3033
import com.skyd.anivu.model.preference.player.BackgroundPlayPreference
3134
import com.skyd.anivu.ui.component.showToast
35+
import com.skyd.anivu.ui.mpv.PlayerCommand
3236
import com.skyd.anivu.ui.mpv.PlayerViewRoute
3337
import com.skyd.anivu.ui.mpv.copyAssetsForMpv
3438
import com.skyd.anivu.ui.mpv.service.PlayerService
39+
import kotlinx.coroutines.flow.collect
40+
import kotlinx.coroutines.flow.combine
41+
import kotlinx.coroutines.launch
3542
import java.io.File
3643

3744

@@ -75,10 +82,22 @@ class PlayActivity : BaseComposeActivity() {
7582
override fun onServiceConnected(className: ComponentName, service: IBinder) {
7683
val binder = service as PlayerService.PlayerServiceBinder
7784
this@PlayActivity.service = binder.getService().apply {
78-
if (uri != Uri.EMPTY && viewModel.uri.value == Uri.EMPTY) {
79-
viewModel.uri.tryEmit(uri)
85+
if (!playerState.value.path.isNullOrBlank() && viewModel.currentPath.value.isNullOrBlank()) {
86+
viewModel.currentPath.tryEmit(playerState.value.path)
87+
}
88+
lifecycleScope.launch {
89+
combine(
90+
viewModel.currentPath,
91+
viewModel.title,
92+
viewModel.thumbnail,
93+
) { currentPath, title, thumbnail ->
94+
if (currentPath != null) {
95+
onCommand(PlayerCommand.SetPath(currentPath, title, thumbnail))
96+
}
97+
}.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect()
8098
}
8199
}
100+
82101
serviceBound = true
83102
}
84103

@@ -124,15 +143,10 @@ class PlayActivity : BaseComposeActivity() {
124143
addOnNewIntentListener(listener)
125144
onDispose { removeOnNewIntentListener(listener) }
126145
}
127-
val uri by viewModel.uri.collectAsStateWithLifecycle()
128-
val title by viewModel.title.collectAsStateWithLifecycle()
129-
val thumbnail by viewModel.thumbnail.collectAsStateWithLifecycle()
130-
if (uri != Uri.EMPTY) {
146+
val path by viewModel.currentPath.collectAsStateWithLifecycle()
147+
if (path != null) {
131148
PlayerViewRoute(
132149
service = if (serviceBound) service else null,
133-
uri = uri,
134-
title = title,
135-
thumbnail = thumbnail,
136150
onBack = { finish() },
137151
onSaveScreenshot = {
138152
picture = it

app/src/main/java/com/skyd/anivu/ui/activity/player/PlayerViewModel.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import com.skyd.anivu.ext.imageLoaderBuilder
1313
import com.skyd.anivu.ui.activity.player.PlayActivity.Companion.VIDEO_THUMBNAIL_KEY
1414
import com.skyd.anivu.ui.activity.player.PlayActivity.Companion.VIDEO_TITLE_KEY
1515
import com.skyd.anivu.ui.activity.player.PlayActivity.Companion.VIDEO_URI_KEY
16+
import com.skyd.anivu.ui.mpv.resolveUri
1617
import dagger.hilt.android.lifecycle.HiltViewModel
1718
import kotlinx.coroutines.flow.MutableStateFlow
1819
import kotlinx.coroutines.launch
1920
import javax.inject.Inject
2021

2122
@HiltViewModel
2223
class PlayerViewModel @Inject constructor() : ViewModel() {
23-
val uri: MutableStateFlow<Uri> = MutableStateFlow(Uri.EMPTY)
24+
val currentPath: MutableStateFlow<String?> = MutableStateFlow(null)
2425
val title: MutableStateFlow<String?> = MutableStateFlow(null)
2526
val thumbnail: MutableStateFlow<Bitmap?> = MutableStateFlow(null)
2627

@@ -30,7 +31,7 @@ class PlayerViewModel @Inject constructor() : ViewModel() {
3031
val uri = IntentCompat.getParcelableExtra(
3132
intent, VIDEO_URI_KEY, Uri::class.java
3233
) ?: intent.data ?: return
33-
this.uri.tryEmit(uri)
34+
this.currentPath.tryEmit(uri.resolveUri(appContext))
3435
this.title.tryEmit(intent.getStringExtra(VIDEO_TITLE_KEY))
3536
viewModelScope.launch {
3637
val bitmapUrl = intent.getStringExtra(VIDEO_THUMBNAIL_KEY)

app/src/main/java/com/skyd/anivu/ui/component/shape/SemiCircle.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import androidx.compose.ui.graphics.Path
77
import androidx.compose.ui.graphics.Shape
88
import androidx.compose.ui.unit.Density
99
import androidx.compose.ui.unit.LayoutDirection
10-
import com.skyd.anivu.ui.mpv.controller.ForwardRippleDirect
10+
import com.skyd.anivu.ui.mpv.land.controller.ForwardRippleDirect
1111

1212
class ForwardRippleShape(private val direct: ForwardRippleDirect) : Shape {
1313
override fun createOutline(

app/src/main/java/com/skyd/anivu/ui/mpv/MPVPlayer.kt

+29-28
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import com.skyd.anivu.model.preference.player.HardwareDecodePreference
1515
import com.skyd.anivu.model.preference.player.PlayerMaxBackCacheSizePreference
1616
import com.skyd.anivu.model.preference.player.PlayerMaxCacheSizePreference
1717
import com.skyd.anivu.model.preference.player.PlayerSeekOptionPreference
18-
import com.skyd.anivu.ui.mpv.controller.bar.toDurationString
18+
import com.skyd.anivu.ui.mpv.land.controller.bar.toDurationString
1919
import `is`.xyz.mpv.MPVLib
2020
import `is`.xyz.mpv.MPVLib.mpvFormat.MPV_FORMAT_DOUBLE
2121
import `is`.xyz.mpv.MPVLib.mpvFormat.MPV_FORMAT_FLAG
@@ -91,6 +91,7 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
9191
MPVLib.setOptionString("save-position-on-quit", "no")
9292
// would crash before the surface is attached
9393
MPVLib.setOptionString("force-window", "no")
94+
MPVLib.setOptionString("vo", "null")
9495
// "no" wouldn't work and "yes" is not intended by the UI
9596
MPVLib.setOptionString("idle", "yes")
9697
MPVLib.setPropertyString("sub-fonts-dir", fontDir)
@@ -119,7 +120,6 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
119120
MPVLib.setOptionString("display-fps-override", refreshRate.toString())
120121
MPVLib.setOptionString("video-sync", "audio")
121122

122-
MPVLib.setOptionString("vo", vo)
123123
MPVLib.setOptionString("gpu-context", "android")
124124
MPVLib.setOptionString("opengl-es", "yes")
125125
MPVLib.setOptionString(
@@ -232,7 +232,7 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
232232
}
233233
}
234234

235-
data class Track(val trackId: Int, val name: String)
235+
data class Track(val trackId: Int, val name: String, val isAlbumArt: Boolean)
236236

237237
private var tracks = mapOf<String, MutableList<Track>>(
238238
"audio" to mutableListOf(),
@@ -258,28 +258,9 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
258258
}
259259

260260
fun loadTracks() {
261-
for (list in tracks.values) {
262-
list.clear()
263-
// pseudo-track to allow disabling audio/subs
264-
list.add(Track(-1, context.getString(R.string.track_off)))
265-
}
266-
val count = MPVLib.getPropertyInt("track-list/count") ?: 0
267-
// Note that because events are async, properties might disappear at any moment
268-
// so use ?: continue instead of !!
269-
for (i in 0 until count) {
270-
val type = MPVLib.getPropertyString("track-list/$i/type") ?: continue
271-
if (!tracks.containsKey(type)) {
272-
Log.w(TAG, "Got unknown track type: $type")
273-
continue
274-
}
275-
val mpvId = MPVLib.getPropertyInt("track-list/$i/id") ?: continue
276-
val lang = MPVLib.getPropertyString("track-list/$i/lang")
277-
val title = MPVLib.getPropertyString("track-list/$i/title")
278-
279-
tracks.getValue(type).add(
280-
Track(trackId = mpvId, name = getTrackDisplayName(mpvId, lang, title))
281-
)
282-
}
261+
loadTrack("sub")
262+
loadTrack("audio")
263+
loadTrack("video")
283264
}
284265

285266
fun loadSubtitleTrack() = loadTrack("sub")
@@ -288,7 +269,13 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
288269
private fun loadTrack(trackType: String) {
289270
tracks[trackType]!!.apply {
290271
clear()
291-
add(Track(-1, context.getString(R.string.track_off)))
272+
add(
273+
Track(
274+
trackId = -1,
275+
name = context.getString(R.string.track_off),
276+
isAlbumArt = false,
277+
)
278+
)
292279
}
293280
val count = MPVLib.getPropertyInt("track-list/count")
294281
// Note that because events are async, properties might disappear at any moment
@@ -299,9 +286,14 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
299286
val mpvId = MPVLib.getPropertyInt("track-list/$i/id") ?: continue
300287
val lang = MPVLib.getPropertyString("track-list/$i/lang")
301288
val title = MPVLib.getPropertyString("track-list/$i/title")
289+
val isAlbumArt = MPVLib.getPropertyBoolean("track-list/$i/albumart")
302290

303291
tracks.getValue(type).add(
304-
Track(trackId = mpvId, name = getTrackDisplayName(mpvId, lang, title))
292+
Track(
293+
trackId = mpvId,
294+
name = getTrackDisplayName(mpvId, lang, title),
295+
isAlbumArt = isAlbumArt,
296+
)
305297
)
306298
}
307299
}
@@ -342,11 +334,15 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
342334
// Property getters/setters
343335
val filename: String?
344336
get() = MPVLib.getPropertyString("filename")
337+
val path: String?
338+
get() = MPVLib.getPropertyString("path")
345339
val mediaTitle: String?
346340
get() = MPVLib.getPropertyString("media-title")
347341
var paused: Boolean
348342
get() = MPVLib.getPropertyBoolean("pause")
349343
set(paused) = MPVLib.setPropertyBoolean("pause", paused)
344+
val playlistCount: Int
345+
get() = MPVLib.getPropertyInt("playlist-count") ?: 0
350346
val isIdling: Boolean
351347
get() = MPVLib.getPropertyBoolean("idle-active")
352348
val eofReached: Boolean
@@ -502,6 +498,10 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
502498
MPVLib.command(arrayOf("loadfile", filePath))
503499
}
504500

501+
fun playlistPrev() {
502+
MPVLib.command(arrayOf("playlist-prev"))
503+
}
504+
505505
fun stop() {
506506
MPVLib.command(arrayOf("stop"))
507507
}
@@ -543,7 +543,8 @@ class MPVPlayer(private val context: Application) : SurfaceHolder.Callback, MPVL
543543

544544
fun screenshot(onSaveScreenshot: (File) -> Unit) {
545545
val format = "jpg"
546-
val filename = "$filename-(${timePos.toDurationString(splitter = "-")})-${Random.nextInt()}"
546+
val filename =
547+
"$filename-(${timePos.toLong().toDurationString(splitter = "-")})-${Random.nextInt()}"
547548
MPVLib.setOptionString("screenshot-format", format)
548549
MPVLib.setOptionString("screenshot-template", filename)
549550
MPVLib.command(arrayOf("screenshot"))

app/src/main/java/com/skyd/anivu/ui/mpv/PlayerCommand.kt

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
package com.skyd.anivu.ui.mpv
22

33
import android.graphics.Bitmap
4-
import android.net.Uri
54
import android.view.Surface
65
import android.view.SurfaceHolder
76
import androidx.compose.ui.geometry.Offset
87
import com.skyd.anivu.ui.mpv.MPVPlayer.Track
8+
import com.skyd.anivu.ui.mpv.service.CustomMediaData
99
import java.io.File
1010

1111
sealed interface PlayerCommand {
1212
data class Attach(val surfaceHolder: SurfaceHolder) : PlayerCommand
1313
data class Detach(val surface: Surface) : PlayerCommand
14-
data class SetUri(val uri: Uri) : PlayerCommand
15-
data class SetTitle(val title: String) : PlayerCommand
16-
data class SetThumbnail(val thumbnail: Bitmap) : PlayerCommand
14+
data class SetPath(
15+
val path: String, val title: String? = null, val thumbnail: Bitmap? = null
16+
) : PlayerCommand
17+
1718
data object Destroy : PlayerCommand
18-
data class Paused(val paused: Boolean, val uri: Uri?) : PlayerCommand
19+
data class Paused(val paused: Boolean) : PlayerCommand
1920
data object PlayOrPause : PlayerCommand
20-
data class SeekTo(val position: Int) : PlayerCommand
21+
data class SeekTo(val position: Long) : PlayerCommand
2122
data class Rotate(val rotate: Int) : PlayerCommand
2223
data class Zoom(val zoom: Float) : PlayerCommand
2324
data class VideoOffset(val offset: Offset) : PlayerCommand
@@ -36,12 +37,12 @@ sealed interface PlayerEvent {
3637
data class Position(val value: Long) : PlayerEvent
3738
data class Duration(val value: Long) : PlayerEvent
3839
data class MediaTitle(val value: String) : PlayerEvent
39-
data class Title(val value: String) : PlayerEvent
40+
data class CustomData(val path: String, val value: CustomMediaData) : PlayerEvent
4041
data class Paused(val value: Boolean) : PlayerEvent
4142
data class PausedForCache(val value: Boolean) : PlayerEvent
4243
data object Seek : PlayerEvent
4344
data object EndFile : PlayerEvent
44-
data object FileLoaded : PlayerEvent
45+
data class FileLoaded(val path: String?) : PlayerEvent
4546
data object PlaybackRestart : PlayerEvent
4647
data class Zoom(val value: Float) : PlayerEvent
4748
data class VideoOffsetX(val value: Float) : PlayerEvent
@@ -50,6 +51,8 @@ sealed interface PlayerEvent {
5051
data class Speed(val value: Float) : PlayerEvent
5152
data class AllSubtitleTracks(val tracks: List<Track>) : PlayerEvent
5253
data class SubtitleTrackChanged(val trackId: Int) : PlayerEvent
54+
data class AllVideoTracks(val tracks: List<Track>) : PlayerEvent
55+
data class VideoTrackChanged(val trackId: Int) : PlayerEvent
5356
data class AllAudioTracks(val tracks: List<Track>) : PlayerEvent
5457
data class AudioTrackChanged(val trackId: Int) : PlayerEvent
5558
data class Buffer(val bufferDuration: Int) : PlayerEvent
@@ -60,5 +63,4 @@ sealed interface PlayerEvent {
6063
data class Artist(val value: String) : PlayerEvent
6164
data class Album(val value: String) : PlayerEvent
6265
data class MediaThumbnail(val value: Bitmap?) : PlayerEvent
63-
data class Thumbnail(val value: Bitmap?) : PlayerEvent
6466
}

app/src/main/java/com/skyd/anivu/ui/mpv/PlayerUtil.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal fun Uri.resolveUri(context: Context): String? {
1717
"file" -> path
1818
"content" -> openContentFd(context)
1919
"http", "https", "rtmp", "rtmps", "rtp", "rtsp",
20-
"mms", "mmst", "mmsh", "tcp", "udp", "lavf" -> this.toString()
20+
"mms", "mmst", "mmsh", "tcp", "udp", "lavf", "fd" -> this.toString()
2121

2222
else -> null
2323
}

0 commit comments

Comments
 (0)