Skip to content

Commit 661ed0b

Browse files
committed
feat(release): version 1.2.3 - player settings integration, styling refactoring, & consent updates
Integrates player settings with ExoPlayer and MPV, refactors UI appearance settings, and applies diagnostic privacy constraints. Detailed Changes: 1. Player Settings Integration (ExoPlayer & MPV): - Propagated user settings (hardware decoding, audio passthrough, surround sound, subtitles auto-select, seek duration, play in background, frame rate matching) from the repository to player engines. - ExoPlayer: Custom software-only MediaCodecSelector when hardware decoding is off; custom AudioCapabilities based on passthrough/surround settings; disabled embedded ASS styles. - MPV: Configured option flags ("hwdec", "audio-spdif", "audio-channels") based on user settings. - UI: Hooked up custom seek durations to skip gestures, implemented background pausing lifecycle observer, auto-subtitles toggle, and display refresh rate matching for TV modes. 2. Torrent Statistics & Playback Start Fixes: - Reset torrent download metrics immediately on URI changes to prevent stale UI during transitions. - Filtered active stream metrics by infoHash to avoid stale warnings. 3. Background Streaming Service & Diagnostics: - Allowed dynamic stop of foreground notification service when toggled off. - Configured app to force restart cleanly on foreground settings change. - Fixed a race condition where toggling "Server Foreground Service" off and restarting would result in the setting not being saved because SharedPreferences `.apply()` was interrupted by the immediate `Runtime.exit(0)` call. Changed to `.commit()` to write synchronously in AuthRepository. - Relocated server settings/status card, removing redundant connection tests. 4. UI Component Polish: - PosterComponents: Aligned 'In Cinema' clapperboard badge and improved episode count layout with a stacked shadow card effect. - Settings Restructure: Migrated Player UI Style setting to Player Settings panel, and relocated Liquid Glass Lab to Interface Settings. - Removed analytics opt-in prompt on launch, exposing a manual disclosure trigger in Privacy settings. 5. Build & Dependency Updates: - Added packaging exclusions for duplicate protobuf files to fix assemble builds. - Integrated Firebase Performance Monitoring plugin. - Disabled Ad Tracking permissions (AD_ID) and personalizations in AndroidManifest.xml. - Disabled PostHog Session Replays.
1 parent 6a98b50 commit 661ed0b

22 files changed

Lines changed: 458 additions & 200 deletions

app/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
id("org.jetbrains.kotlin.plugin.compose")
66
id("com.google.gms.google-services")
77
id("com.google.firebase.crashlytics")
8+
id("com.google.firebase.firebase-perf")
89
}
910

1011
fun stringPropertyOrEnv(name: String): String? =
@@ -41,6 +42,13 @@ android {
4142
compileSdk = 37
4243
ndkVersion = "29.0.13846066"
4344

45+
packaging {
46+
resources {
47+
excludes.add("google/protobuf/**")
48+
excludes.add("META-INF/gradle/incremental.annotation.processors")
49+
}
50+
}
51+
4452
defaultConfig {
4553
applicationId = "com.stremio.mobile"
4654
minSdk = 24
@@ -144,6 +152,7 @@ dependencies {
144152
implementation(platform("com.google.firebase:firebase-bom:34.15.0"))
145153
implementation("com.google.firebase:firebase-analytics")
146154
implementation("com.google.firebase:firebase-crashlytics")
155+
implementation("com.google.firebase:firebase-perf")
147156
implementation("com.posthog:posthog-android:3.51.0")
148157
testImplementation("junit:junit:4.13.2")
149158
}

app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
34
<uses-permission android:name="android.permission.INTERNET" />
5+
<!-- Completely disable Google Play Services Advertising ID permission merging -->
6+
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />
47
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
58
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
69
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -21,6 +24,22 @@
2124
android:theme="@style/Theme.StremioMobile"
2225
android:usesCleartextTraffic="true">
2326

27+
<!-- Disable Firebase Advertising ID collection and ad personalization signals -->
28+
<meta-data
29+
android:name="google_analytics_adid_collection_enabled"
30+
android:value="false" />
31+
<meta-data
32+
android:name="google_analytics_default_allow_ad_personalization_signals"
33+
android:value="false" />
34+
35+
<!-- Disable Facebook Advertiser ID collection and auto event logging -->
36+
<meta-data
37+
android:name="com.facebook.sdk.AutoLogAppEventsEnabled"
38+
android:value="false" />
39+
<meta-data
40+
android:name="com.facebook.sdk.AdvertiserIDCollectionEnabled"
41+
android:value="false" />
42+
2443
<meta-data
2544
android:name="com.facebook.sdk.ApplicationId"
2645
android:value="@string/facebook_app_id" />

app/src/main/java/com/stremio/mobile/MainApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class MainApplication : Application(), SingletonImageLoader.Factory {
7777
).apply {
7878
captureApplicationLifecycleEvents = true
7979
captureScreenViews = true
80-
sessionReplay = true
80+
sessionReplay = false
8181
sessionReplayConfig.maskAllTextInputs = true
8282
sessionReplayConfig.maskAllImages = true
8383
optOut = !analyticsEnabled

app/src/main/java/com/stremio/mobile/data/repository/AuthRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class AuthRepository(
7575

7676
fun isServerInForeground(): Boolean = preferences.getBoolean("server_in_foreground", true)
7777
fun setServerInForeground(enabled: Boolean) {
78-
preferences.edit().putBoolean("server_in_foreground", enabled).apply()
78+
preferences.edit().putBoolean("server_in_foreground", enabled).commit()
7979
}
8080

8181
fun isMobileDataWarning(): Boolean = preferences.getBoolean("mobile_data_warning", true)

app/src/main/java/com/stremio/mobile/data/repository/PlaybackRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class PlaybackRepository(
5555
}
5656
val preferredLang = runCatching { core.getSubtitleSettings().language }.getOrNull()
5757
val startPositionMs = runCatching { core.getResumePositionMs(option.core.streamRequest) }.getOrDefault(0L)
58+
val settings = runCatching { core.getCtx().profile.settings }.getOrNull()
5859

5960
playbackManager.load(
6061
uri = Uri.parse(url),
@@ -63,6 +64,7 @@ class PlaybackRepository(
6364
subtitles = subtitles,
6465
preferredSubtitleLang = preferredLang,
6566
engine = engine,
67+
settings = settings,
6668
)
6769
return true
6870
}

app/src/main/java/com/stremio/mobile/player/ExoStreamPlayer.kt

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import kotlinx.coroutines.isActive
2929
import kotlinx.coroutines.launch
3030
import java.util.Locale
3131

32-
class ExoStreamPlayer(context: Context) : Player {
32+
class ExoStreamPlayer(
33+
context: Context,
34+
private val settings: com.stremio.core.types.profile.Profile.Settings? = null
35+
) : Player {
3336
override val engine: PlayerEngine = PlayerEngine.EXO
3437

3538
private val appContext = context.applicationContext
@@ -46,8 +49,52 @@ class ExoStreamPlayer(context: Context) : Player {
4649
val dataSourceFactory = DefaultDataSource.Factory(appContext, httpDataSourceFactory)
4750
val mediaSourceFactory = DefaultMediaSourceFactory(appContext)
4851
.setDataSourceFactory(dataSourceFactory)
49-
val renderersFactory = DefaultRenderersFactory(appContext)
50-
.setEnableDecoderFallback(true)
52+
53+
val hardwareDecoding = settings?.hardwareDecoding ?: true
54+
val renderersFactory = object : DefaultRenderersFactory(appContext) {
55+
override fun buildAudioSink(
56+
context: Context,
57+
enableFloatOutput: Boolean,
58+
enableAudioTrackPlaybackParams: Boolean
59+
): androidx.media3.exoplayer.audio.AudioSink {
60+
val audioPassthrough = settings?.audioPassthrough ?: false
61+
val surroundSound = settings?.surroundSound ?: false
62+
val audioCapabilities = if (!audioPassthrough || !surroundSound) {
63+
val encodings = if (audioPassthrough) {
64+
intArrayOf(
65+
android.media.AudioFormat.ENCODING_PCM_16BIT,
66+
android.media.AudioFormat.ENCODING_AC3,
67+
android.media.AudioFormat.ENCODING_E_AC3,
68+
android.media.AudioFormat.ENCODING_DTS,
69+
android.media.AudioFormat.ENCODING_DTS_HD
70+
)
71+
} else {
72+
intArrayOf(android.media.AudioFormat.ENCODING_PCM_16BIT)
73+
}
74+
val maxChannels = if (surroundSound) 8 else 2
75+
androidx.media3.exoplayer.audio.AudioCapabilities(encodings, maxChannels)
76+
} else {
77+
androidx.media3.exoplayer.audio.AudioCapabilities.getCapabilities(context)
78+
}
79+
return androidx.media3.exoplayer.audio.DefaultAudioSink.Builder(context)
80+
.setAudioCapabilities(audioCapabilities)
81+
.setEnableFloatOutput(enableFloatOutput)
82+
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
83+
.build()
84+
}
85+
}.setEnableDecoderFallback(true)
86+
87+
if (!hardwareDecoding) {
88+
renderersFactory.setMediaCodecSelector { mimeType, requiresSecure, requiresTunneling ->
89+
val decoders = androidx.media3.exoplayer.mediacodec.MediaCodecSelector.DEFAULT
90+
.getDecoderInfos(mimeType, requiresSecure, requiresTunneling)
91+
decoders.filter { info ->
92+
val name = info.name.lowercase()
93+
name.startsWith("omx.google.") || name.startsWith("c2.android.") || name.contains(".sw.") || name.endsWith(".sw")
94+
}
95+
}
96+
}
97+
5198
ExoPlayer.Builder(appContext)
5299
.setMediaSourceFactory(mediaSourceFactory)
53100
.setRenderersFactory(renderersFactory)
@@ -110,6 +157,7 @@ class ExoStreamPlayer(context: Context) : Player {
110157
startPositionMs: Long,
111158
subtitles: List<ExternalSubtitle>,
112159
preferredSubtitleLang: String?,
160+
settings: com.stremio.core.types.profile.Profile.Settings?,
113161
) {
114162
currentUri = uri
115163
currentStartPositionMs = startPositionMs
@@ -230,7 +278,7 @@ class ExoStreamPlayer(context: Context) : Player {
230278
playerView?.subtitleView?.let { subView ->
231279
val style = currentSubtitleStyle
232280
subView.setApplyEmbeddedFontSizes(false)
233-
subView.setApplyEmbeddedStyles(style.assStyling)
281+
subView.setApplyEmbeddedStyles(false)
234282
subView.setFractionalTextSize(0.0533f * (style.sizePercent / 100f))
235283

236284
val textColor = parseSubtitleColor(style.textColor, Color.WHITE)
@@ -320,6 +368,11 @@ class ExoStreamPlayer(context: Context) : Player {
320368
error: String? = mutableRuntimeState.value.error,
321369
ended: Boolean = exoPlayer.playbackState == androidx.media3.common.Player.STATE_ENDED,
322370
) {
371+
val videoFormat = exoPlayer.videoFormat
372+
val videoWidth = videoFormat?.width ?: 0
373+
val videoHeight = videoFormat?.height ?: 0
374+
val videoFrameRate = videoFormat?.frameRate ?: 0f
375+
323376
mutableRuntimeState.value = PlayerRuntimeState(
324377
isPlaying = exoPlayer.isPlaying,
325378
isBuffering = exoPlayer.playbackState == androidx.media3.common.Player.STATE_BUFFERING,
@@ -332,6 +385,9 @@ class ExoStreamPlayer(context: Context) : Player {
332385
subtitlesDisabled = exoPlayer.trackSelectionParameters.disabledTrackTypes.contains(C.TRACK_TYPE_TEXT),
333386
error = error,
334387
ended = ended,
388+
videoWidth = videoWidth,
389+
videoHeight = videoHeight,
390+
videoFrameRate = videoFrameRate,
335391
)
336392
}
337393

app/src/main/java/com/stremio/mobile/player/MpvStreamPlayer.kt

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import kotlinx.coroutines.flow.StateFlow
1717
import kotlinx.coroutines.isActive
1818
import kotlinx.coroutines.launch
1919

20-
class MpvStreamPlayer(context: Context) : Player {
20+
class MpvStreamPlayer(
21+
context: Context,
22+
private val settings: com.stremio.core.types.profile.Profile.Settings? = null
23+
) : Player {
2124
override val engine: PlayerEngine = PlayerEngine.MPV
2225

2326
private val appContext = context.applicationContext
@@ -108,6 +111,7 @@ class MpvStreamPlayer(context: Context) : Player {
108111
copyMpvAssets(appContext)
109112
return StremioMpvView(
110113
context = context,
114+
settings = settings,
111115
onSurfaceReady = {
112116
surfaceReady = true
113117
applyResizeMode()
@@ -134,6 +138,7 @@ class MpvStreamPlayer(context: Context) : Player {
134138
startPositionMs: Long,
135139
subtitles: List<ExternalSubtitle>,
136140
preferredSubtitleLang: String?,
141+
settings: com.stremio.core.types.profile.Profile.Settings?,
137142
) {
138143
currentUri = uri
139144
currentStartPositionMs = startPositionMs
@@ -315,6 +320,10 @@ class MpvStreamPlayer(context: Context) : Player {
315320
val subtitleTracks = parsedSubtitleTracks.map { enrichSubtitleTrack(it) }
316321
val sid = runCatching { MPVLib.getPropertyString("sid") }.getOrNull()
317322

323+
val width = runCatching { MPVLib.getPropertyInt("width") }.getOrNull() ?: 0
324+
val height = runCatching { MPVLib.getPropertyInt("height") }.getOrNull() ?: 0
325+
val fps = runCatching { MPVLib.getPropertyDouble("container-fps") }.getOrNull()?.toFloat() ?: 0f
326+
318327
mutableRuntimeState.value = PlayerRuntimeState(
319328
isPlaying = !paused && !buffering && error == null && !ended,
320329
isBuffering = buffering || (!fileLoaded && error == null && !ended),
@@ -327,6 +336,9 @@ class MpvStreamPlayer(context: Context) : Player {
327336
subtitlesDisabled = sid == "no" || subtitleTracks.none { it.selected },
328337
error = error,
329338
ended = ended,
339+
videoWidth = width,
340+
videoHeight = height,
341+
videoFrameRate = fps,
330342
)
331343
}
332344

@@ -348,9 +360,9 @@ class MpvStreamPlayer(context: Context) : Player {
348360
exclusive = external.exclusive,
349361
)
350362
}
351-
352363
private class StremioMpvView(
353364
context: Context,
365+
private val settings: com.stremio.core.types.profile.Profile.Settings?,
354366
private val onSurfaceReady: () -> Unit,
355367
private val onSurfaceDestroyed: () -> Unit,
356368
) : BaseMPVView(context, null) {
@@ -359,8 +371,29 @@ class MpvStreamPlayer(context: Context) : Player {
359371
MPVLib.setOptionString("profile", "fast")
360372
MPVLib.setOptionString("gpu-context", "android")
361373
MPVLib.setOptionString("opengl-es", "yes")
362-
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
363-
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
374+
375+
val hardwareDecoding = settings?.hardwareDecoding ?: true
376+
if (hardwareDecoding) {
377+
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
378+
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
379+
} else {
380+
MPVLib.setOptionString("hwdec", "no")
381+
}
382+
383+
val audioPassthrough = settings?.audioPassthrough ?: false
384+
if (audioPassthrough) {
385+
MPVLib.setOptionString("audio-spdif", "ac3,dts,eac3,truehd,dts-hd")
386+
} else {
387+
MPVLib.setOptionString("audio-spdif", "")
388+
}
389+
390+
val surroundSound = settings?.surroundSound ?: false
391+
if (surroundSound) {
392+
MPVLib.setOptionString("audio-channels", "auto-safe")
393+
} else {
394+
MPVLib.setOptionString("audio-channels", "stereo")
395+
}
396+
364397
MPVLib.setOptionString("ao", "audiotrack,opensles")
365398
MPVLib.setOptionString("audio-set-media-role", "yes")
366399
MPVLib.setOptionString("tls-verify", "yes")

app/src/main/java/com/stremio/mobile/player/PlaybackManager.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,19 @@ class PlaybackManager(
2626
subtitles: List<ExternalSubtitle> = emptyList(),
2727
preferredSubtitleLang: String? = null,
2828
engine: PlayerEngine = PlayerEngine.EXO,
29+
settings: com.stremio.core.types.profile.Profile.Settings? = null,
2930
) {
3031
player?.release()
3132
val fallbackMessage = if (engine == PlayerEngine.MPV) "MPV unavailable; using ExoPlayer." else null
3233
player = runCatching {
33-
PlayerFactory.create(context, engine).also {
34-
it.load(uri, startPositionMs, subtitles, preferredSubtitleLang)
34+
PlayerFactory.create(context, engine, settings).also {
35+
it.load(uri, startPositionMs, subtitles, preferredSubtitleLang, settings)
3536
it.play()
3637
}
3738
}.getOrElse { failure ->
3839
if (engine != PlayerEngine.MPV) throw failure
39-
ExoStreamPlayer(context).also {
40-
it.load(uri, startPositionMs, subtitles, preferredSubtitleLang)
40+
ExoStreamPlayer(context, settings).also {
41+
it.load(uri, startPositionMs, subtitles, preferredSubtitleLang, settings)
4142
it.play()
4243
it.reportNonFatalError(fallbackMessage)
4344
}

app/src/main/java/com/stremio/mobile/player/Player.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ data class PlayerSubtitleStyle(
7272
val outlineColor: String = "#000000",
7373
val assStyling: Boolean = true,
7474
)
75-
7675
data class PlayerRuntimeState(
7776
val isPlaying: Boolean = false,
7877
val isBuffering: Boolean = false,
@@ -85,6 +84,9 @@ data class PlayerRuntimeState(
8584
val subtitlesDisabled: Boolean = true,
8685
val error: String? = null,
8786
val ended: Boolean = false,
87+
val videoWidth: Int = 0,
88+
val videoHeight: Int = 0,
89+
val videoFrameRate: Float = 0f,
8890
)
8991

9092
interface Player {
@@ -97,6 +99,7 @@ interface Player {
9799
startPositionMs: Long = 0,
98100
subtitles: List<ExternalSubtitle> = emptyList(),
99101
preferredSubtitleLang: String? = null,
102+
settings: com.stremio.core.types.profile.Profile.Settings? = null,
100103
)
101104
fun retry()
102105
fun play()

app/src/main/java/com/stremio/mobile/player/PlayerFactory.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package com.stremio.mobile.player
33
import android.content.Context
44

55
object PlayerFactory {
6-
fun create(context: Context, engine: PlayerEngine): Player {
6+
fun create(
7+
context: Context,
8+
engine: PlayerEngine,
9+
settings: com.stremio.core.types.profile.Profile.Settings? = null
10+
): Player {
711
return when (engine) {
8-
PlayerEngine.EXO -> ExoStreamPlayer(context)
9-
PlayerEngine.MPV -> MpvStreamPlayer(context)
12+
PlayerEngine.EXO -> ExoStreamPlayer(context, settings)
13+
PlayerEngine.MPV -> MpvStreamPlayer(context, settings)
1014
}
1115
}
1216
}

0 commit comments

Comments
 (0)