Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
006d703
docs: add offline Widevine DRM for HLS design doc
daytime-em Jun 5, 2026
c2833b2
docs: add mermaid diagrams to offline DRM design
daytime-em Jun 5, 2026
3363a19
V1 of doc
daytime-em Jun 5, 2026
5e7bff9
docs: reorder offline DRM design — architecture first, Media3 interna…
daytime-em Jun 5, 2026
a799411
docs: reorder §1 call-site-first, make createHlsDownloadHelper internal
daytime-em Jun 5, 2026
15b899c
docs: add download/keyset lifecycle management (§1.8)
daytime-em Jun 5, 2026
78e2df8
docs: local keyset removal + clarify playbackId→keySetId storage
daytime-em Jun 5, 2026
83011a4
docs: download top video + all audio & subtitle renditions
daytime-em Jun 5, 2026
61d9582
docs: offline playback & enumeration (§1.9)
daytime-em Jun 5, 2026
a13f7e9
docs: route offline playback through MuxMediaSourceFactory by URL scheme
daytime-em Jun 5, 2026
255ebb5
docs: add ListenableFuture Java-compat enumeration variants
daytime-em Jun 9, 2026
2ac5d24
Merge remote-tracking branch 'origin/main' into feature/offline-drm
daytime-em Jul 17, 2026
4e7b630
docs: add offline Widevine DRM for HLS design doc
daytime-em Jun 5, 2026
5d77ea4
docs: add mermaid diagrams to offline DRM design
daytime-em Jun 5, 2026
e559b07
V1 of doc
daytime-em Jun 5, 2026
728b1f1
docs: reorder offline DRM design — architecture first, Media3 interna…
daytime-em Jun 5, 2026
b3cc730
docs: reorder §1 call-site-first, make createHlsDownloadHelper internal
daytime-em Jun 5, 2026
f0c8b6a
docs: add download/keyset lifecycle management (§1.8)
daytime-em Jun 5, 2026
e79733f
docs: local keyset removal + clarify playbackId→keySetId storage
daytime-em Jun 5, 2026
f8de178
docs: download top video + all audio & subtitle renditions
daytime-em Jun 5, 2026
95dee27
docs: offline playback & enumeration (§1.9)
daytime-em Jun 5, 2026
78e6602
docs: route offline playback through MuxMediaSourceFactory by URL scheme
daytime-em Jun 5, 2026
c76f9a8
docs: add ListenableFuture Java-compat enumeration variants
daytime-em Jun 9, 2026
6062093
Merge branch 'feature/offline-drm' of github.com:muxinc/mux-player-an…
daytime-em Jul 22, 2026
78ec161
NAT-451: Add Offline-Download Service and Provider object (#99)
daytime-em Jul 23, 2026
6489a6f
NAT-449: Add DownloadHelper and OfflineLicenseHelper that work with o…
daytime-em Jul 23, 2026
cdafcc0
typo fix
daytime-em Jul 27, 2026
019169a
NAT-454: Offline Download API (#101)
daytime-em Jul 30, 2026
c883e36
InternalUtils.executePost should actually set its HTTP Method (#102)
daytime-em Jul 30, 2026
8acaf10
NAT-485: Add playback path for offline downloads (#103)
daytime-em Aug 5, 2026
ed380e2
NAT-485: Add example screen for offline downloads/DRM (#104)
daytime-em Aug 5, 2026
a6681a0
Handle license expiry
daytime-em Aug 12, 2026
ab22071
Remove the design doc, no longer needed and doesn't fully match anymore
daytime-em Aug 12, 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
13 changes: 13 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
}

android {
Expand All @@ -18,6 +19,7 @@ android {

buildFeatures {
viewBinding = true
compose = true
}
buildTypes {
release {
Expand All @@ -39,6 +41,17 @@ dependencies {
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.activity.ktx)

// Compose, used by the offline-downloads example
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
debugImplementation(libs.androidx.compose.ui.tooling)

implementation(project(":library"))

testImplementation(libs.junit)
Expand Down
37 changes: 37 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- ===== Mux Player offline downloads =====
Only required if the app uses Mux Player's offline download support. The mux-player library
intentionally does not declare these, since they are sensitive and shouldn't be forced on
apps that don't download media. See the matching <service> block below in <application>. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<!-- Required for DownloadService progress notifications on API 33+ (also request at runtime) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Lets PlatformScheduler restart downloads after a device reboot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- ===== end Mux Player offline downloads ===== -->

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down Expand Up @@ -30,6 +42,11 @@
android:name=".examples.SmartCacheActivity"
android:exported="false"
android:label="Smart Caching Example" />
<activity
android:name=".examples.offline.OfflineDownloadsActivity"
android:exported="false"
android:label="Offline Downloads Example"
android:theme="@style/Theme.MuxVideoMedia3.NoActionBar" />
<activity
android:name=".MainActivity"
android:exported="true"
Expand All @@ -40,6 +57,26 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- ===== Mux Player offline downloads =====
Only required if the app uses Mux Player's offline download support. Paired with the
permissions declared above at the top of the manifest. -->
<service
android:name="com.mux.player.offline.MuxDownloadService"
android:foregroundServiceType="dataSync"
android:exported="false">
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<!-- JobService used by PlatformScheduler (MuxDownloadService.getScheduler()) to reschedule
downloads when their requirements are met and after a reboot -->
<service
android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
<!-- ===== end Mux Player offline downloads ===== -->
</application>

</manifest>
5 changes: 5 additions & 0 deletions app/src/main/java/com/mux/player/media3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.mux.player.media3.examples.BasicPlayerActivity
import com.mux.player.media3.examples.ConfigurablePlayerActivity
import com.mux.player.media3.examples.SmartCacheActivity
import com.mux.player.media3.examples.carousel.PlayerCarouselActivity
import com.mux.player.media3.examples.offline.OfflineDownloadsActivity

class MainActivity : AppCompatActivity() {

Expand Down Expand Up @@ -73,6 +74,10 @@ class MainActivity : AppCompatActivity() {
title = "Smart Caching",
destination = Intent(this@MainActivity, SmartCacheActivity::class.java)
),
Example(
title = "Offline Downloads",
destination = Intent(this@MainActivity, OfflineDownloadsActivity::class.java)
),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.mux.player.media3.examples.offline

import androidx.media3.common.MediaItem
import com.mux.player.media.MediaItems
import com.mux.player.media3.PlaybackIds

/**
* One Mux asset this example knows how to download.
*
* @param title A human-readable name, shown in the UI. Not used by the download itself.
* @param playbackId The Mux playback ID. This is also the download's ID.
* @param playbackToken Required for signed playback and DRM. See [MediaItems.fromMuxPlaybackId].
* @param drmToken Required for DRM playback. DRM assets need *both* tokens.
*/
data class DownloadableAsset(
val title: String,
val playbackId: String,
val playbackToken: String? = null,
val drmToken: String? = null,
) {

/**
* The [MediaItem] to hand to [com.mux.player.offline.MuxDownloadManager.startDownload]. It's an
* ordinary Mux MediaItem — the same one you'd use for streaming playback.
*/
fun toMediaItem(): MediaItem = MediaItems.fromMuxPlaybackId(
playbackId = playbackId,
playbackToken = playbackToken,
drmToken = drmToken,
)
}

/**
* The assets offered on the 'Select Asset' screen. Compiled in for the sake of the example; a real
* app would fetch its catalog from its own backend.
*/
object DownloadableAssets {

val all: List<DownloadableAsset> = listOf(
DownloadableAsset(
title = "Tears of Steel",
playbackId = PlaybackIds.TEARS_OF_STEEL,
),
DownloadableAsset(
title = "The Making of Big Buck Bunny",
playbackId = PlaybackIds.MAKING_OF_BUCK_BUNNY,
),
DownloadableAsset(
title = "Elephants Dream",
playbackId = PlaybackIds.ELEPHANTS_DREAM,
),
DownloadableAsset(
title = "Mux Marketing Video",
playbackId = PlaybackIds.MUX_MARKETING_VIDEO,
),
// To download a DRM asset, add both tokens, which your backend generates:
// DownloadableAsset(
// title = "My DRM Asset",
// playbackId = "...",
// playbackToken = "...",
// drmToken = "...",
// ),
)

/**
* The title for [playbackId], or the playback ID itself if it isn't in [all]. Downloads outlive
* the catalog they came from (they're on disk between app launches), so the UI can't assume a
* download's asset is still listed.
*/
fun titleFor(playbackId: String): String =
all.firstOrNull { it.playbackId == playbackId }?.title ?: playbackId
}
Loading
Loading