Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7174620
Player: hold the controls' glass to its tint while they fade
professorDeveloper Nov 7, 2026
f788c94
Player on iOS 26+: the phone's own clear Liquid Glass under the controls
professorDeveloper Nov 7, 2026
c039276
Player on iOS 26+: the controls are the phone's own Liquid Glass buttons
professorDeveloper Nov 8, 2026
bb4e44d
Player native bars: review fixes
professorDeveloper Nov 8, 2026
8cc963c
Native player bars: system-sized glyphs, untinted glass, no alpha on …
professorDeveloper Nov 8, 2026
8d6e6ab
Player on iOS 26+: Apple's symbols, pull-down menus, iOS sheets
professorDeveloper Nov 8, 2026
f65c912
Player on iPhone: choose the controls style
professorDeveloper Nov 8, 2026
c1577d1
Player on iPhone: classic controls by default, offer the native ones …
professorDeveloper Nov 8, 2026
1d65475
Detail tabs: keep panes alive and stop rebuilding the page on a switch
professorDeveloper Nov 8, 2026
5a17663
Detail tabs: decode Similar posters and cast avatars at drawn size
professorDeveloper Nov 8, 2026
d169de1
Decrypt KissKH .txt1 subtitles on the device
professorDeveloper Nov 8, 2026
9f8d25c
Read extractor answers as plain JSON on iOS and macOS
professorDeveloper Nov 8, 2026
d3a3820
Merge branch 'fix/kisskh-mobile' into native-liquid-player
professorDeveloper Nov 8, 2026
53cd833
Return device extractor answers as JSON text
professorDeveloper Nov 8, 2026
151519d
Read Zangetsu metadata whatever shape it arrives in
professorDeveloper Nov 8, 2026
5c5cca0
Player: Android PiP, audio focus, media session and brightness fixes
professorDeveloper Nov 8, 2026
37cda38
Fix player logic and UX across platforms
professorDeveloper Nov 8, 2026
a24940c
Merge branch 'fix/player-android-system' into native-liquid-player
professorDeveloper Nov 8, 2026
fe2eef7
Merge branch 'fix/player-logic-ux' into native-liquid-player
professorDeveloper Nov 8, 2026
3bd4a9c
App-wide data, reliability and performance fixes
professorDeveloper Nov 8, 2026
1f0b30d
Merge branch 'fix/app-perf-data' into native-liquid-player
professorDeveloper Nov 8, 2026
9e66cda
Open deep links over Home from one consumer, keeping the viewer's place
professorDeveloper Nov 8, 2026
14b4000
Android polish: predictive back, themed icon, no background polling
professorDeveloper Nov 8, 2026
3113114
Make catalog chips and the settings side-nav focusable; mouse back pops
professorDeveloper Nov 8, 2026
ac5a4b4
Merge branch 'fix/deeplinks-android' into native-liquid-player
professorDeveloper Nov 8, 2026
db3e4f6
Cantonese: write crash log with the house term 紀錄
professorDeveloper Nov 8, 2026
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: 2 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ dependencies {
// FrameExtractor: seek-bar frames for HLS (preview/HlsFramePreview.kt).
implementation("androidx.media3:media3-inspector:$media3")
implementation("androidx.media3:media3-datasource-okhttp:$media3")
// MediaSessionCompat for the player: headset and Bluetooth buttons.
implementation("androidx.media:media:1.7.0")

// CloudStream provider runtime (Android-only feature). The `library` module
// carries MainAPI/APIHolder/`app` HTTP client/extractors/BasePlugin so .cs3
Expand Down
8 changes: 7 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
android:hardwareAccelerated="true"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:enableOnBackInvokedCallback="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
Expand All @@ -76,6 +77,11 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Links go to DeeplinkService (app_links) alone; the router
taking them too raced it. -->
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
Expand Down
96 changes: 96 additions & 0 deletions android/app/src/main/kotlin/com/soplay/sozo/AudioFocusHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.soplay.sozo

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build

/**
* Audio focus and ACTION_AUDIO_BECOMING_NOISY for the players that do not
* handle them themselves (libmpv). Events go to [onEvent] as the wire names
* the Dart AudioFocusPolicy reads: gain, loss, loss_transient, duck, noisy.
*/
class AudioFocusHelper(
private val context: Context,
private val onEvent: (String) -> Unit,
) {
private val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var hasFocus = false
private var request: AudioFocusRequest? = null
private var noisyReceiver: BroadcastReceiver? = null

private val listener = AudioManager.OnAudioFocusChangeListener { change ->
when (change) {
AudioManager.AUDIOFOCUS_GAIN -> {
hasFocus = true
onEvent("gain")
}
AudioManager.AUDIOFOCUS_LOSS -> {
hasFocus = false
onEvent("loss")
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> onEvent("loss_transient")
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> onEvent("duck")
}
}

fun request(): Boolean {
if (hasFocus) return true
val granted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val req = request ?: AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
.build()
)
.setOnAudioFocusChangeListener(listener)
.build()
.also { request = it }
audio.requestAudioFocus(req)
} else {
@Suppress("DEPRECATION")
audio.requestAudioFocus(listener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
}
hasFocus = granted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
return hasFocus
}

fun abandon() {
if (!hasFocus) return
hasFocus = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
request?.let { audio.abandonAudioFocusRequest(it) }
} else {
@Suppress("DEPRECATION")
audio.abandonAudioFocus(listener)
}
}

fun watchNoisy() {
if (noisyReceiver != null) return
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) onEvent("noisy")
}
}
val filter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
@Suppress("UnspecifiedRegisterReceiverFlag")
context.registerReceiver(receiver, filter)
}
noisyReceiver = receiver
}

fun release() {
abandon()
noisyReceiver?.let { runCatching { context.unregisterReceiver(it) } }
noisyReceiver = null
}
}
Loading
Loading