# Android SDK とネイティブ Media3

Android プレーヤー をビデオ制御、ドラッグ可能なプレイリスト、保存されたシャッフル/レパート、オフラインの履歴書でダウンロードします。 DRM-X またはネイティブ Media3 を使用して保護された再生を統合します。

**プレーヤー を選択し、1 つのライセンス サービスを保持します。**利用する DRM-X 6.0 Android SDK ガイドされた統合のために、またはあなたのネイティブを維持します Media3 プレーヤー。 両方ともあなたの認証されたバックエンドに接続し、 Widevine. ザ・ Android SDK ストリーミングと持続的なオフラインをサポート VOD 互換性あり API 携帯電話、タブレット、テレビ、埋め込みを含む23台以上のデバイス Android システム。 

## Android プレーヤー [ #](#try-android-app) を試す



**ダウンロード, インストール, プレスプレイ.**無料の評価アプリは、パブリックデモビデオとサインを自動的に使用しています。 APK を構築したり、 DRM-X 認証情報 を入力する必要はありません。 

[ Androidのサンプル APK ](https://docs.drm-x.com/downloads/android/1.0.0-preview.12/drmx-android-player-1.0.0-preview.12.apk)をダウンロード



**1.0.0-preview.12** · 7.02 MB · Android 6.0+ (API 23+) · DRM-X 6.0 Player · 更新日 2026-09-14


- このページをAndroidの携帯電話で開き、APKをダウンロードしてください。
- ダウンロードしたファイルを開きます。 Android に尋ねると、使用したブラウザまたはファイルマネージャからインストールを許可し、 **Install** をタップします。
- **DRM-X 6.0 Player** を開き、接続を維持し、ビデオの **Play** ボタンをタップします。 このアプリは、再生を押すときにストリーミングライセンスを要求します。
- 品質、オーディオ、キャプション、再生速度を選択するには、**Settings**を開きます。 電話を回転させ、フルスクリーンを継続します。
- タップ **ダウンロード** 現在のビデオのためにまたは **プレイリストをダウンロード** 詳しくはこちら VOD アイテム。 ダウンロードは、最大1080pまでの最高サポートライセンスのレンディションを使用しています L1、または480pの L3. **オフラインで再生** 完成したローカルプレイリストを次の/前の、シャッフル、繰り返し、自動再生で開きます。 **オンラインで行く** フルプレイリストに戻る。 **新着情報** 接続時に選択したライセンスを更新します。



すでにキュートされたメディアのダウンロードは、バックグラウンドで続行します。 Android がライセンスやトラックの準備中にアプリを停止すると、残りのアイテムの準備を完了するために接続されている間、再オープンします。 既存のダウンロードが保持されます。 オフライン再生は、キャッシュされたメディアのみを使用します。 不完全なまたは期限切れのダウンロードは、完了または更新が必要です。



Widevine のプロビジョニング障害 (Media3 6002) は、認証されたデモサーバーのリレーを公式の Google サービスに使用できるようになりました。 L3 CDM の機能が SD キーを受け取ることができます。 動作しない Widevine の実装を持つデバイスは、デバイス/ファームウェアの修正が必要です。 ライセンスは、欠落 DRM サポートを追加することはできません。



サンプル APK は公開され、評価のために署名されます。AAR と完全な SDK は、対象のコンソールアカウントによって利用可能に残ります。このデモとは別にキーとバックエンドのログインを署名する独自の生産アプリをキーに保ちましょう。

APK チェックサムと USB インストール

SHA-256:

```
8fa044b82c0abab7ce2a987f349524fd7ad1e78f3c897771f4764cb2aa521180
```

Android プラットフォームツールと認証済み USB デバイスを使って:

```
adb install -r drmx-android-player-1.0.0-preview.12.apk
```

アンインストールせずに、既存の公式サンプルにこのAPKをインストールします。同じ署名キーとアプリケーションIDは、ダウンロード、オフラインライセンス、保存された位置と設定を保存します。あなた自身のビルドが別の署名キーを使用している場合は、別々のアプリケーションIDを使用して、両方のアプリを維持し、サンプルのオフラインライセンスを保存します。





## Android プレーヤーのプレビューで新しいもの。12[#](#latest-player-update)



リリース14 9月2026。 ダウンロードした再生を修正し、予期しないライセンス要求時に Widevine ライセンスの有効期限が切れるか、または1分未満の残りがあります。 これにより、 Media3 エラー `6004`, , , HTTP 接続中のエラー、または DNS 切断中のエラー。



プレーヤー は、ダウンロードしたライセンスをローカルで復元します。有効なライセンスは残りの再生時間を保持します。期限切れのライセンスは ** の接続を表示し、Renew** をタップします。ライセンスの有効性と保護ルールは、デバイスによって強制されます。ネットワーク操作が失敗したときに数値 HTTP ステータスも含まれ、リクエストヘッダ、URL、ライセンス応答を露出せずに。


- 既存の公式アプリに Preview.12 をインストールします。現在のアプリデータを保存し、ダウンロードしてください。
- WayMakerまたは別のダウンロードしたビデオが更新する必要がある場合は、インターネットに接続し、そのビデオを選択し、**Renew**をタップします。
- **Licenseが更新されるのを待ってください。 オフラインで再生する準備ができました。** を選択し、**オフラインで再生**を選択します。 暗号化されたビデオはダウンロードされています。



このリリースは、preview.11 の制御とプレイリスト機能を保持します。パブリック評価アプリとその埋め込まれた プレーヤー は、preview.12 を識別します。別々に配布されたコンソール SDK は、preview.10 / バンドル 20260910.8 のままです。





## Android プレーヤーのプレビューで新しいもの。11[#](#player-preview11)



2026年9月14日リリース。この評価アプリは、最新の プレーヤー コントロールとプレイリストの改善、一貫性のあるインタラクションスタイルを Android と Apple プレーヤー に含まれています。


- ** 動画内のコントロール:** は Play/Pause を中心とし、マルチビデオプレイリスト、CC、設定、フルスクリーンボタンを次へ。 再生中に 2 秒後に非表示をコントロールします。 ビデオをタップすると、再びそれらを明らかにします。 
- **キャプションと再生設定:** は CC を使用して、利用可能な字幕を表示または非表示にし、設定はキャプション言語、オーディオ、再生速度、および許可された品質を選択します。 オフラインの選択肢は、ダウンロードに含まれるトラックのみを使用します。 
- **Drag to reorder:** は、プレイリストのアイテムのハンドルをドラッグして、その位置を変更します。 保存された注文は、アプリの再起動を生き、ダウンロードしたプレイリストに反映されます。 リオーダーは、現在のビデオ再生を維持します。 アクセス可能な移動/アクションを移動するためのハンドルをタップします。 
- ** 保存されたシャッフルとリピート:** 選択したモードは、オンラインでとオフラインの再生で再起動を強調し、存続します。シャッフルとオフの両方で、ビデオは保存された注文に従い、最終アイテムの後に停止します。プレイリストメニューの「次を再生」アクションが削除されました。 
- ** 停止した場所の再開:** 各ビデオの保存した VOD 位置は、ストリーミングやダウンロードした再生に戻るときに復元されます。 進行状況は、現在のビューアのためにこのデバイスに保存されます。 デバイス間で同期しません。 



プレーヤー の変更は、再生中にドラッグするプレイリストを含む、Samsung S24 Ultra で検証されました。 再起動後保存された注文、再生履歴書、Wi-Fi とモバイルデータが無効な再生をダウンロードしました。 オフライン表示は、完全なダウンロードと使用可能な、オフラインライセンスが必要です。



このリリースでは、パブリック評価 APK が更新されます。 別々に配布されたコンソール SDK は `1.0.0-preview.10` のままで、`20260910.8` をバンドルします。 その既存の統合手順は、そのバンドルを参照してください。





## 1。 統合パスを選択[#](#choose-your-path)



[PATH A ・ QUICKEST START**DRM-X プレーヤー**

DrmXPlayer、電話サンプル、オフラインマネージャを使用してください。 独自のログインとビジネスルールを保持してください。

](#android)[PATH B・KEEP YOUR UI**Media3 + DRM-X ヘルパー**

既存の プレーヤー を保ち、スタックをダウンロードしましょう。DrmXOfflineManager は永続的なライセンスを管理しましょう。

](#existing-media3)[PATH C・C NATIVE ONLY**AARのないMedia3**

同じバックエンド契約とネイティブライセンスのライフサイクルを自分で実装します。 DRM-X プレーヤー の依存関係はありません。

](#configure-media3) 

[Console → Integrations から Android SDK の ZIP または AAR をダウンロードできます](https://6.drm-x.com/console/integrations)。有効な**Professional（$499/月）または Enterprise**の組織が対象です。Trial と Standard のアカウントでは SDK をダウンロードできません。ネイティブ Media3 サンプルを含む本ドキュメントは公開されていますが、再生には権限のあるアカウント、公開済みコンテンツ、対応デバイスが必要です。



現在のプレビュー: `1.0.0-preview.10` · 顧客バンドル `20260910.8` · Media3 `1.11.0`。 ZIP にはローカル Maven リポジトリ、Gradle wrapper、注釈付き README、電話/ TV/native サンプル、 `apks/` にインストール可能な電話サンプル APK、およびオフライン統合ノートが含まれています。 顧客 認証情報 や保護された映画はありません。



## 2. コンテンツとバックエンドを準備[#](#before-you-code)


- DRM-X 環境で Widevine 互換暗号化 DASH のタイトルをパッケージ化して公開します。 ** が Content ID ** を発行しました。
- サーバのEnvironment ID、Site Key、Access Key を構成します。 作業開始点に [ をダウンロードできる PHP インテグレーション](https://developer.drm-x.com/php-sample/) を使用します。
- ユーザーのログインと認証チェックを実行します。アプリは、あなたのバックエンドを短命のユーザートークンと呼びます。あなたのバックエンドは、コンテンツ、ポリシー、信頼できるURLを選択します。
- 物理的な Widevine 対応デバイスでオンラインでの再生を最初に実行します。 ストリーミング契約が機能した後にのみオフライン取得を追加します。



暗号化された動画と DRM ライセンスは別の経路で配信されます

CDN は暗号化された動画を配信します。バックエンドが視聴権限を確認し、DRM ライセンスサービスが対応デバイスでの復号を許可します。

- 01**ストレージ / CDN**

暗号化されたメディアをプレーヤーに配信します。

- 02**お客様のバックエンド**

視聴権限を確認し、DRM-X に再生の認可をリクエストします。

- 03**DRM-X**

再生の認可情報を検証し、DRM ライセンスリクエストを処理します。

- 04**プレーヤー + デバイス**

デバイスの DRM システムを使ってライセンスを取得し、暗号化されたメディアを再生します。

媒体の配達および免許証配達は別です。顧客バックエンドがDRM-Xサーバー認証情報を保持するだけ。 

| この値を変更する | 所属する場所 | 意味する | 
| --- | --- | --- |

| sessionEndpoint | Android | HTTPS の再生セッションエンドポイントは、生のライセンス サーバー URL ではありません。 | 

| offlineEndpoint | Android, オフライン のみ | 取得/更新/リリース承認エンドポイント。 | 

| contentId | Android リクエスト; バックエンドの検証 | 公開保護された Content ID のビューアは、アクセスする権利があります。 | 

| 顧客アクセストークン | ログイン後のアプリメモリ | 短いユーザートークンは、Site Key や Access Key ということはありません。 | 

| Environment ID、Site Key、Access Key | サーバシークレット構成のみ | DRM-Xアカウント認証情報。APK、HTML、またはソースコントロールでそれらを出荷しないでください。 | 

| 方針と信頼できるマニフェストURL | お客様のバックエンド | ビジネスの資格とパッケージのマッピング。アプリからこれらを受け入れないでください。 | 



## 3. SDK[#](#install-sdk)をインストールして実行する



Android Studio で抽出された ZIP のルートフォルダーを開きます。 JDK 17, Android SDK 36 と Android Gradle Plugin 9.2.0 を使用します。 含まれているラッパーは Gradle 9.4.1 を使用します。 最小デバイス API は 23 です。 依存関係の構築には、インターネットアクセスが必要です。 `samples.phone` を選択し、実行します。 その初期値は、パブリックデモバックエンドとタイトルを使用します。 自分のバックエンドに切り替える前に、サインインとストリーミングコントロールに従ってください。



```
# Windows
gradlew.bat :samples:phone:assembleDebug
# macOS / Linux
sh gradlew :samples:phone:assembleDebug
```



既存のアプリでは、ZIP の `maven/` フォルダをプロジェクトのルートにコピーします。 `settings.gradle.kts`:



```
dependencyResolutionManagement {
    repositories {
        maven { url = uri(rootDir.resolve("maven")) }
        google()
        mavenCentral()
    }
}
```



アプリケーションモジュールの`build.gradle.kts`で:



```
dependencies {
    implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")
}
```

代わりにスタンドアローン AAR をインストール

app モジュールの `libs/` に AAR を置きます。 フラット AAR は自動的な依存関係の解決を持たないので、3つの一致 Media3 の依存関係を自分で追加します。 両方のフォームをインストールしないでください。

```
implementation(files("libs/drmx-universal-player-1.0.0-preview.10.aar"))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")
```



`AndroidManifest.xml` では、インターネットアクセスを許可し、この最小限のサンプルのバックアップを無効にします。 バックアップを必要とするプロダクションアプリでは、特にデバイス・バウンド・オフライン・ライセンス・メタデータを除外します。



```
<uses-permission android:name="android.permission.INTERNET" />
<application android:allowBackup="false" android:usesCleartextTraffic="false" ... >
```



携帯電話のサンプルの検索 `MainActivity.kt` 詳しくはこちら `drmx.sessionEndpoint`, `drmx.offlineEndpoint`, `drmx.contentId` そして、 `customer.loginEndpoint`. マークされたフォールバックの設定を変更するか、ログイン後にアプリケーションからこれらのインテントの追加を渡すか。 パス `customer.accessToken` ランタイムで。 ZIP README 各パラメータを記述し、どのデモが置換するのかを説明します。



## AAR [#](#build-apk-from-aar)を使用してテストAPKをビルドする



この例では、実行可能な電話プロジェクトをフルSDK ZIPから使用し、スタンドアローンAARを明示的にリンクします。 同じアプローチは、既存の Androidアプリで動作します。


- [full Android SDK ZIP ](https://6.drm-x.com/console/integrations)をダウンロードし、抽出します。 Android Studioのルートフォルダを開きます。
- Keep `libs/drmx-universal-player-1.0.0-preview.10.aar` in the extracted root. Use JDK 17, Android SDK 36 and the included Gradle wrapper.
- `samples/phone/build.gradle.kts` では、DRM-X Maven の依存関係を `dependencies` 内の次のエントリに置き換えます。そのファイルの残りの部分を保ちましょう。



```
implementation(files(rootProject.file("libs/drmx-universal-player-1.0.0-preview.10.aar")))
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")
```



削除する `implementation("com.haihaisoft.drmx:drmx-universal-player:1.0.0-preview.10")` 同じように SDK 2回は同梱されていません。 フラット AAR 3つのニーズ Media3 上記の依存関係を示します。


- ダウンロードしたデモを自分のテストビルドと一緒にインストールしておくには、電話モジュールの`applicationId`を`com.example.drmxtest`に変更します。このサンプルに名前空間は変更されません。
- ビルド APKそれからそれを取付けて下さい USB-コネクティッド Android デバイスと USB 認可されたデバッグ。



```
# Windows, from the extracted SDK root
gradlew.bat :samples:phone:assembleDebug
adb install -r samples/phone/build/outputs/apk/debug/phone-debug.apk

# macOS / Linux
sh gradlew :samples:phone:assembleDebug
adb install -r samples/phone/build/outputs/apk/debug/phone-debug.apk
```



テストアプリを開き、Playをタップします。 付属の電話ソースは、最初に同じパブリックデモを使用します。 独自のタイトルをテストするには、マークされたセッション/オフライン/ログインエンドポイントを置き換え、`MainActivity.kt` で Content ID を発行し、ランタイムで独自のユーザーの短命認証を提供します。 あなたのサーバーは Site Key と Access Key を保持します。



コンパクトなコントロールは、AAR で `DrmXPlayerView` から来ます。 提供されたアクティビティは、プレイツーオーサライズ、応答性回転、所有権のダウンロード、オフライン再生、および更新を実証します。 このビューを独自のアプリに追加する際に、以下の [ の制御統合例 ](#player-controls) を参照してください。





## 4. サーバーおよび要求のデフォルトポリシー[#](#server-request)



Android アプリケーションは、再生セッションのバックエンドを要求します。 バックエンドは、ユーザを認証し、エン資格を検証し、Content ID を公開したメディアにマップし、 DRM-X を呼び出します。 完全なストリーミング契約の [ プレイバックセッション API ](https://docs.drm-x.com/ja/api-reference/playback-sessions) を参照してください。



```
{
  "contentId": "YOUR-PUBLISHED-CONTENT-ID",
  "contentType": "vod",
  "drmSystem": "widevine",
  "playbackMode": "streaming",
  "platformCapabilities": {
    "clientPlatform": "android",
    "drmSystems": ["widevine"], "manifestTypes": ["dash"],
    "maximumHeight": 1080, "secureDecoder": false, "persistentState": false
  }
}
```



上記の保守的な機能は Widevine L1 を主張しません。 SDK の機能ヘルパー、または、実際のデバイスに対する独自のデバイス評価を使用してください。 クライアント機能は、互換性の選択を支援します。 それらは質の高い資格を付与しません。



PHP は、オンラインの再生のために `multi-tier-standard` にデフォルトをサンプルします。パッケージが 1 つのキーを使用するときに、文書化された単一キー ポリシーを選択します。あなたのポリシーは、パッケージのキー モデルとビューアの権利に一致する必要があります。サーバーの構成または `DRMX_LICENSE_POLICY_TEMPLATE` 環境設定を変更し、 プレーヤー のクエリパラメータではありません。[ ライセンスポリシー](https://docs.drm-x.com/ja/license-service/policies)を参照してください。



**オフラインでは、独自の永続的なポリシーが必要です。**セットセット `playback_policy.persistent` サーバが発行するポリシーで真に、ライセンス、レンタル、再生、更新期間を選択します。 参照は、それぞれ7日間/ 3日/ 48時間/ 7日間使用されます。 これらは、例えばビジネスルール、スタジオの承認やプラットフォーム全体のデフォルトではありません。 一致するメタデータを返します。 Widevineライセンスは、権限を維持します。



## 5. パスA:DrmXPlayer[#](#android)を通して遊びます



アクティビティ内では、`PlayerView` を作成し、ログイン後、安全なウィンドウを使用して `prepare` を呼び出します。 セッションのラッパー交換は、Widevine を設定し、返された最大高さを適用し、停止時にストリーミング予約を解放します。 インポートは `com.haihaisoft.drmx.player` と AndroidX Media3 から来ます。



```
val view = PlayerView(this)
setContentView(view)
DrmXPlayer.protectWindow(window)
val player = DrmXPlayer(this, view)
val capabilities = WidevineCapabilities.playbackCapabilities(
    clientPlatform = "android",
    displayMetrics = resources.displayMetrics,
    applicationVersion = "1.0",
)
player.prepare(
    endpoint = "https://customer.example/api/drmx/playback-session",
    contentId = "YOUR-PUBLISHED-CONTENT-ID",
    contentType = "vod", // use live only for a published live title
    capabilities = capabilities,
    customerAuthenticationHeaders = mapOf("Authorization" to "Bearer $userToken"),
)
// Keep player as an Activity/Fragment-owned field in your actual application.
// Stop when your playback lifecycle ends; close when the owner is destroyed.
// player.stop()
// player.close()
```



`DrmXPlayerListener` を認証、準備、再生、エラー UI に登録します。 ライセンストークンやレスポンスボディをダンプするのではなく、安全なエラーコードを表示します。 含まれた電話サンプルを使用して、ライフサイクル、フルスクリーン制御、キューをダウンロードします。



## プレーヤーの制御および中断されていない回転[#](#player-controls)



動画で**Play**をタップすると、承認が開始されます。 画面または設定を開くと、ストリーミングライセンスが取得されません。 ミューズ/再開、設定を変更し、電話を回転させると、現在のセッションが保存されます。



![DRM-X Android プレーヤー サムスン S24 Ultra の設定、品質、オーディオトラック、サブタイトル/CC、プレイバック速度とスリープタイマーを表示します。](https://docs.drm-x.com/assets/android-player-settings-preview4.png)サムスン S24 Ultra の実際の設定。 行をタップすると、利用可能なオプションが表示されます。 オーディオサブメニューには、完全な言語名が表示されます。 利用可能な品質と言語は、コンテンツ、ライセンス、デバイスによって異なります。 

| コントロール | 顧客ができること | 
| --- | --- |

| 品質・品質 | マニフェスト内の対応済みかつライセンスで許可された解像度、または Auto を選択してください。選択できない項目には、ライセンスやデバイス/DRM による制限理由が表示されます。Auto にも同じ制限が適用されます。 | 

| オーディオトラック | パッケージによって供給されるサポートされている音声言語を選択してください。 | 

| サブタイトル/CC | キャプチャ言語またはオフを選択します。 サポートされているキャプションが存在し、それらを直接切り替えるときにCCボタンが表示されます。 | 

| キャプションの出現 | フォント、テキストの色とサイズ、背景/ウィンドウの色と不透明度を調整します。 設定はデバイスに保存されます。 | 

| 再生速度/スリープタイマー | 選択された間隔の後で0.25–2×の速度で演劇するか、または自動的に停止して下さい。 | 

| Seek / フルスクリーン | タイムラインをスクラブし、10秒を探し、または両面タップします。 再生を再起動せずに風景に回転します。 | 


写真の下の3つのコンパクトな操作は、**ダウンロード**(オフラインでの使用のために)、**オフラインで再生**と**更新**(オフラインライセンス)です。 サンプルは、許可されたビデオ録画とサポートされているオーディオ/サブタイトルトラックをダウンロードします。 オフラインメニューは、ダウンロードしたメディアのみを公開します。 更新には、接続が必要です。



**ログのコピー** 情報フッターのコピーはテキストとして最近のエラーをコピーするので、スクリーンショットなしでサポート情報を送信できます。 レポートにはレポートが含まれています ID、操作、 SDK/Android/deviceバージョン、数値 CDM/Media3 エラー, 選択したスタックフレーム, 接続/ダウンロード/再生状態と繰り返し数. 彼らは除外します 認証情報、媒体の URL、キー、キー セット ID、免許証データおよび完全な CDM ダンプ。



サンプルは、最大32のローカルレポート(64)を保持します。 KB) 5時間。 接続および署名されたとき、それは30秒に一度に構成された顧客のバックエンドに永続的な間違いを、アップロードします。 パブリックデモサーバーは5時間警告/エラーを保持し、繰り返されたでき事を結合し、512でファイルをキャップします KB 毎分プルーン。 ルーチンの進行と成功した再生は記録されません。 カスタムの統合は、明示的に独自の同じオリジンを供給する必要があります `drmx.diagnosticsEndpoint`;診断の間違いは決して再生を中断しません。



**オフラインライセンスは既に使用していますか?**Preview.5 は、CDM 368 を「異なるセッションでロードされたライセンス」と報告した S24 のケースを修正しました。 オフラインで再生を押すと、既存の プレーヤー が再び再開されます。 まず、DRM セッションを再更新し、保存されたライセンスを更新します。 ダウンロードと永続的なライセンスは保存されます。 app-data リセットは必要ありません。 

```
val player = DrmXPlayer(this)
val controls = DrmXPlayerView(this)
controls.bind(player) // Does not acquire a session or license.
controls.onPlayRequested = {
    player.prepare(endpoint = sessionEndpoint, contentId = contentId,
        capabilities = capabilities, customerAuthenticationHeaders = userHeaders)
}
controls.onFullScreenRequested = { /* resize the existing player view */ }
player.listener = object : DrmXPlayerListener {
    override fun onAuthorizing(contentId: String) { controls.setPreparing(true) }
    override fun onReady(info: DrmXPlaybackInfo) { controls.setPreparing(false) }
    override fun onError(error: PlaybackSessionException) { controls.setPreparing(false) }
}
// Keep protectWindow(window) and the secure video surface.
// At final owner destruction: controls.unbind(); player.close()
```



供給された電話マニフェストは、オリエンテーションと画面サイズの変更を処理します。 `onConfigurationChanged` は同じアクティビティと プレーヤー のサイズを変更します。 両方の部分を合わせると、両方の部分を保持します。 アクティビティを再作成するホストは、再生所有者を保持し、ビューを再添付する必要があります。 プロセスの死は、回転とは異なるであり、通常の状態の復元を必要とします。 4K/8K オプションには、マッチングコンテンツ、ライセンスのエンタイトルとデバイス/ DRM サポートが必要です。 CDM は、承認者のままです。



カスタム プレーヤー では、アクティブなオフライン DRM セッションを止めて、同じ永続的なライセンスを更新または解放します。 SDK の `player.stopForOfflineLicenseOperation { /* call manager here */ }` は、Media3 の再生キューがメディアを解放するのを待ちます。 平野 `stop()` は非同期的に動作するキューを待ちます。 同じライセンスが再生されている間、別の `checkLicense()` を実行しないでください。 再生自体は CDM の状態を検証します。 付属のサンプルは、ハンドの完全な更新と無効な操作をオンに表示します。



## 6. ライセンスを取得し、メディアをダウンロード[#](#offline-download)



オフライン再生の準備

ダウンロードの完了と、デバイスに紐づく有効なライセンスの両方が必要です。

- 01**暗号化メディアをダウンロードする**

オンラインの間に、動画と必要な音声・字幕トラックをダウンロードします。

- 02**永続ライセンスを取得する**

オンラインの間に、デバイスに紐づく Widevine ライセンスを取得します。不透明な Key-set ID は非公開で保存してください。

- 03**準備状況を確認する**

ダウンロードが完了し、ライセンスが有効であることを確認します。

- 04**オフラインで再生する**

キャッシュ済みメディアを読み込み、ローカルのライセンスを復元します。更新と解放にはインターネット接続が必要です。

Ready オフライン = 暗号化されたメディア + 有効なデバイス-bound ライセンスを完了します。 一方、他のデバイスを置き換えます。 

```
val offlineManager = DrmXOfflineManager(applicationContext)
val capabilities = WidevineCapabilities.offlinePlaybackCapabilities(
    clientPlatform = "android", // android-tv or android-automotive where applicable
    displayMetrics = resources.displayMetrics,
    applicationVersion = "1.0",
)
offlineManager.acquireLicense(
    endpoint = offlineEndpoint,
    contentId = contentId,
    capabilities = capabilities,
    customerAuthenticationHeaders = customerHeaders,
) { result ->
    result.onSuccess { stored ->
        // Next: select tracks <= stored.maximumHeight and queue their download.
        // Acquisition alone does not download the video.
    }.onFailure { /* show a safe retry/error state */ }
}
```



SDK は不透明な Widevine key-set ID と機密情報を含まないメタデータを保存します。ライセンスは CDM が保持します。DRM License Tokens、未加工のキー、チャレンジ、ライセンス応答のバイト列は永続保存しません。`com.haihaisoft.drmx.offline.licenses.v1.xml` をバックアップ対象から除外してください。



1つのアプリケーション所有のMedia3 `SimpleCache`、ダウンロードマネージャとデータベースを作成します。ライセンスされたMediaItemを使用して`DownloadHelper`を選択し、`maximumHeight`内のビデオを選択します。 Media3 1.11では、準備されたコールバックは`onPrepared(helper, tracksInfoUpdated)`です。 電話サンプルの`enqueueDownload`と`OfflineTestRuntime`は実行可能な実装を示しています。



```
// Inside DownloadHelper's prepared callback, after selecting licensed video:
helper.addAudioLanguagesToSelection("en", "zh-Hans", "zh")
helper.addTextLanguagesToSelection(true, "en", "zh-Hans", "zh")
val selected = helper.getDownloadRequest(offlineLicenseId, byteArrayOf())
val request = offlineManager.createDownloadRequest(contentId, selected.streamKeys)
downloadManager.addDownload(request)
helper.release()
```



オフラインで表示するすべての言語をダウンロードします。 ストリームキーなしで`createDownloadRequest`を呼び出すと、一般的に適応ビデオのために不十分であるすべてのトラックをダウンロードします。 ダウンロードの進行状況を表示し、`Download.STATE_COMPLETED`を待ちます。 生産の背景転送のために、Media3 `DownloadService`、ストレージの制限とAndroid foreground-service/notificationの要件を統合します。 デモキューは、プロセス内で実行されます。



**マルチキーオフラインコンテンツ:**Media3 は 1 つのキーセット ID を再生項目に復元します。その永続的なライセンスには、選択したビデオ、オーディオ、その他の暗号化されたトラックに必要なすべてのキーが含まれている必要があります。実際のパッケージとサーバーポリシーでこれをテストしてください。複数のセッションを有効にすると、独立したオフラインライセンスを結合しません。



## 7. パスB:あなたの既存のMedia3 プレーヤー [#](#existing-media3)と復元して下さい



完全なダウンロードを読んで、その正確なストリームキーを使用します。 ダウンロードと再生スタックは、同じキャッシュの場所とキーマッピングを共有しなければなりません。 この例では、厳密なオフライン再生のための上流アクセスを意図的に無効にします。



```
val cacheOnly = CacheDataSource.Factory()
    .setCache(downloadCache)
    .setUpstreamDataSourceFactory(null)
val mediaSourceFactory = DefaultMediaSourceFactory(this)
    .setDataSourceFactory(cacheOnly)
val nativePlayer = ExoPlayer.Builder(this)
    .setMediaSourceFactory(mediaSourceFactory)
    .build()
val completed = downloadIndex.getDownload(offlineLicenseId)
require(completed?.state == Download.STATE_COMPLETED)
val playback = offlineManager.createPlayback(contentId, streamKeys = completed!!.request.streamKeys)
nativePlayer.setMediaItem(playback.mediaItem)
nativePlayer.prepare()
nativePlayer.play()
// Release nativePlayer when the owner is destroyed.
```



Path A で同じオフラインフローを使用するには、この `mediaSourceFactory` で `DrmXPlayer` をビルドし、 `player.prepareOffline(playback)` を呼び出します。通常の復元では、新しいものを要求せずにローカル CDM ライセンスを使用します。接続されていない受信中にキャッシュエントリがエラーになるはずですが、インターネットにサイレントに切り替えることはありません。



## 8. パスC: ネイティブMedia3、DRM-X AAR[#](#configure-media3)



DrmXPlayer または DrmXOfflineManager を使用する必要はありません。ステップ 3 から 3 つの Media3 の依存関係を追加し、同じ顧客のバックエンド契約を実行します。 `session.manifestUrl` (`session.manifest.url` ではありません)、返されたライセンス URL およびバインドされた認証ヘッダーを使用します。 返された最大高さを適用し、ストリーミングセッションのリリースをストップ、交換、終了、故障、破壊に処理します。



以下の完全なネイティブアクティビティは、この SDK リリースと一緒に `samples:native` としてコンパイルされます。 これは、 HTTPS を処理し、バインドされたレスポンスの読み込み、背景の承認、ストールレスポンス、および予約リリースを処理します。 マークされたエンドポイント/ Content ID を置換し、独自のログイントークンを供給します。 この例は、開始点です。 生産のリフレッシュ、アカウントスイッチ、アプリナビゲーションのライフサイクルを追加します。

完全なネイティブストリーミング活動(Kotlin、DRM-Xの依存関係なし)

```
package com.haihaisoft.drmx.samples.nativeplayer

import android.app.Activity
import android.os.Bundle
import android.view.WindowManager
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URI
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

/** Complete native streaming example. No DRM-X AAR or server credentials. */
@UnstableApi
class MainActivity : Activity() {
    private lateinit var player: ExoPlayer
    private lateinit var status: TextView
    private val worker = Executors.newSingleThreadExecutor()
    private val closed = AtomicBoolean(false)
    private var activeSession: JSONObject? = null
    private var requesting = false
    private var generation = 0
    // CUSTOMIZE: use YOUR authenticated customer backend and Published Content ID.
    // The sample can receive these after your application's login via Intent extras.
    // Never replace the app login token with a DRM-X Site Key or Access Key.
    private val endpoint get() = intent.getStringExtra("drmx.sessionEndpoint")
        ?: "https://customer.example/api/drmx/playback-session"
    private val contentId get() = intent.getStringExtra("drmx.contentId") ?: "YOUR-PUBLISHED-CONTENT-ID"
    private val customerToken get() = intent.getStringExtra("customer.accessToken").orEmpty()

    override fun onCreate(state: Bundle?) {
        super.onCreate(state)
        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
        val root = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL; fitsSystemWindows = true; setPadding(24, 24, 24, 24) }
        status = TextView(this).apply { text = "Native Media3 · connect your backend, then Play"; textSize = 16f }
        val view = PlayerView(this)
        player = ExoPlayer.Builder(this).build(); view.player = player
        root.addView(status)
        root.addView(view, LinearLayout.LayoutParams(-1, 0, 1f))
        root.addView(Button(this).apply { text = "Play protected video"; setOnClickListener { startPlayback() } })
        root.addView(Button(this).apply { text = "Stop & release session"; setOnClickListener { stopPlayback() } })
        setContentView(root)
        player.addListener(object : Player.Listener {
            override fun onPlayerError(error: PlaybackException) { status.text = "Playback error: ${error.errorCodeName}"; releaseSession() }
            override fun onPlaybackStateChanged(state: Int) { if (state == Player.STATE_ENDED) releaseSession() }
        })
    }

    private fun startPlayback() {
        if (requesting) return
        stopPlayback(); requesting = true; status.text = "Authorizing…"
        val attempt = generation
        val request = JSONObject().put("contentId", contentId).put("contentType", "vod")
            .put("drmSystem", "widevine").put("playbackMode", "streaming")
            .put("platformCapabilities", JSONObject().put("clientPlatform", "android")
                .put("drmSystems", org.json.JSONArray(listOf("widevine")))
                .put("manifestTypes", org.json.JSONArray(listOf("dash")))
                .put("maximumHeight", 1080).put("secureDecoder", false).put("persistentState", false))
        val headers = if (customerToken.isBlank()) emptyMap() else mapOf("Authorization" to "Bearer $customerToken")
        worker.execute {
            val result = runCatching { JSONObject(post(endpoint, request.toString(), headers)) }
            runOnUiThread {
                requesting = false
                result.onSuccess { session ->
                    if (closed.get() || attempt != generation) { workerRelease(session); return@onSuccess }
                    // Keep release information even if contract validation fails.
                    activeSession = session
                    runCatching {
                        require(session.getInt("contractVersion") == 1 && session.getString("contentId") == contentId)
                        require(session.getString("drmSystem") == "widevine" && session.getString("playbackMode") == "streaming")
                        val drm = session.getJSONObject("drm")
                        require(drm.getString("system") == "widevine")
                        val maximumHeight = session.getJSONObject("policy").getInt("maximumHeight")
                        require(maximumHeight in 1..4320)
                        val mediaItem = MediaItem.Builder().setUri(https(session.getString("manifestUrl")))
                            .setMimeType("application/dash+xml")
                            .setDrmConfiguration(MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                                .setLicenseUri(https(drm.getString("licenseServerUrl")))
                                .setForceDefaultLicenseUri(true).setLicenseRequestHeaders(authorization(drm))
                                .setMultiSession(false).build()).build()
                        player.trackSelectionParameters = player.trackSelectionParameters.buildUpon()
                            .setMaxVideoSize(Int.MAX_VALUE, maximumHeight).setPreferredAudioLanguages("en")
                            .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true).build()
                        player.setMediaItem(mediaItem); player.prepare(); player.play()
                        status.text = "Native Media3 · protected playback"
                    }.onFailure { status.text = "Invalid session contract. Check your backend."; releaseSession() }
                }.onFailure { if (!closed.get()) status.text = "Authorization failed. Check login, Content ID, policy, and network." }
            }
        }
    }

    private fun releaseSession() { activeSession?.let(::workerRelease); activeSession = null }
    private fun workerRelease(session: JSONObject) {
        // A bounded release may finish after Activity destruction, including a
        // session response that arrived after the user left the screen.
        Thread({ runCatching {
            val release = session.getJSONObject("release")
            val headers = if (release.has("authorization")) authorization(release)
                else authorization(session.getJSONObject("drm"))
            post(release.getString("url"), "", headers)
        } }, "drmx-native-release").start()
    }
    private fun stopPlayback() { generation++; player.stop(); player.clearMediaItems(); releaseSession() }
    override fun onStop() { stopPlayback(); super.onStop() }
    override fun onDestroy() { closed.set(true); stopPlayback(); player.release(); worker.shutdown(); super.onDestroy() }

    companion object {
        fun https(url: String): String {
            val uri = URI(url); require(uri.scheme == "https" && uri.host != null && uri.userInfo == null)
            return url
        }
        fun authorization(drm: JSONObject): Map<String, String> {
            val auth = drm.optJSONObject("authorization")
            val name = auth?.getString("headerName") ?: "Authorization"
            val scheme = auth?.getString("scheme") ?: drm.optString("tokenType", "Bearer")
            val value = auth?.getString("value") ?: drm.getString("drmLicenseToken")
            require(name.matches(Regex("^[A-Za-z0-9-]{1,64}$")) && scheme.matches(Regex("^[A-Za-z][A-Za-z0-9._-]{0,31}$")))
            require(value.isNotBlank() && !value.contains('\r') && !value.contains('\n'))
            return mapOf(name to "$scheme $value")
        }
        fun post(url: String, body: String, headers: Map<String, String>): String {
            val connection = URI(https(url)).toURL().openConnection() as HttpURLConnection
            try {
                connection.requestMethod = "POST"; connection.instanceFollowRedirects = false
                connection.connectTimeout = 5000; connection.readTimeout = 15000; connection.doOutput = true
                connection.setRequestProperty("Content-Type", "application/json")
                headers.forEach { (name, value) -> connection.setRequestProperty(name, value) }
                connection.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
                require(connection.responseCode in 200..299) { "Customer request rejected" }
                return connection.inputStream.use { stream ->
                    val output = java.io.ByteArrayOutputStream(); val buffer = ByteArray(8192)
                    while (true) {
                        val count = stream.read(buffer); if (count < 0) break
                        require(output.size() + count <= 1_048_576); output.write(buffer, 0, count)
                    }
                    output.toString("UTF-8")
                }
            } finally { connection.disconnect() }
        }
    }
}

```



ネイティブオフラインライセンスの場合、使用 `OfflineLicenseHelper.downloadLicense`、persist の不透明のキー セット ID 個人的に復元し、 `MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(id)`. 以下のコンパイルされたヘルパーは、取得、期間チェック、更新、リリース、およびダウンロード/プレイバック項目をカバーしています。 **完全なダウンロードアプリケーションではありません**: キャッシュ、ダウンロードサービス、進捗状況、メタデータ、ユーザー/デバイスバインディング、信頼できる運用権限を保有しています。

ネイティブ永続的ライセンスヘルパー(Kotlin、DRM-Xの依存関係なし)

```
package com.haihaisoft.drmx.samples.nativeplayer

import android.content.Context
import android.util.Base64
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.StreamKey
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.dash.DashUtil
import androidx.media3.exoplayer.drm.DrmSessionEventListener
import androidx.media3.exoplayer.drm.OfflineLicenseHelper
import androidx.media3.exoplayer.offline.DownloadRequest
import org.json.JSONObject

/** Native Media3 alternative: no DRM-X AAR. Call blocking operations on a worker.
 * Application owns downloads/cache, user/device entitlement, progress and lifecycle.
 * Never persist the session, request headers, tokens, challenges or license bytes.
 */
@UnstableApi
class NativeOfflineLicense(context: Context) {
    private val storage = context.getSharedPreferences("native.offline.keysets", Context.MODE_PRIVATE)
    private val upstream = DefaultHttpDataSource.Factory().setConnectTimeoutMs(10000).setReadTimeoutMs(20000)

    // session is the validated response from YOUR offline-license-session endpoint.
    fun acquire(session: JSONObject): ByteArray {
        validate(session)
        val contentId = session.getString("contentId")
        require(!storage.contains(contentId)) { "Renew or release the stored license before acquiring again" }
        val source = upstream.createDataSource()
        val manifest = DashUtil.loadManifest(source, android.net.Uri.parse(MainActivity.https(session.getString("manifestUrl"))))
        val format = DashUtil.loadFormatWithDrmInitData(upstream.createDataSource(), manifest.getPeriod(0))
            ?: error("Missing Widevine initialization data")
        val helper = helper(session)
        try {
            val id = helper.downloadLicense(format)
            save(contentId, id)
            return id
        } finally { helper.release() }
    }

    fun renew(contentId: String, freshSession: JSONObject) {
        validate(freshSession); require(freshSession.getString("contentId") == contentId)
        val helper = helper(freshSession)
        try { save(contentId, helper.renewLicense(keySet(contentId))) } finally { helper.release() }
    }

    // Local CDM check only. No token or network license URI is used.
    fun remainingSeconds(contentId: String): Pair<Long, Long> {
        val config = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).build()
        val helper = OfflineLicenseHelper.newWidevineInstance(config, upstream, DrmSessionEventListener.EventDispatcher())
        try {
            val remaining = helper.getLicenseDurationRemainingSec(keySet(contentId))
            return Pair(remaining.first, remaining.second)
        } finally { helper.release() }
    }

    fun release(contentId: String, freshSession: JSONObject) {
        validate(freshSession); require(freshSession.getString("contentId") == contentId)
        val helper = helper(freshSession)
        try {
            helper.releaseLicense(keySet(contentId))
            check(storage.edit().remove(contentId).commit())
        } finally { helper.release() }
    }

    fun downloadRequest(contentId: String, downloadId: String, manifestUrl: String, streamKeys: List<StreamKey>): DownloadRequest {
        require(streamKeys.isNotEmpty()) { "Select policy-compliant video, audio and subtitle tracks first" }
        return DownloadRequest.Builder(downloadId, android.net.Uri.parse(MainActivity.https(manifestUrl)))
            .setMimeType("application/dash+xml").setStreamKeys(streamKeys).setKeySetId(keySet(contentId)).build()
    }

    fun mediaItem(contentId: String, manifestUrl: String, streamKeys: List<StreamKey>): MediaItem = MediaItem.Builder()
        .setUri(MainActivity.https(manifestUrl)).setMimeType("application/dash+xml").setStreamKeys(streamKeys)
        .setDrmConfiguration(MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).setKeySetId(keySet(contentId)).build()).build()

    private fun helper(session: JSONObject): OfflineLicenseHelper {
        val drm = session.getJSONObject("drm")
        // Use HTTP license headers, NOT Widevine optional key-request parameters.
        val config = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
            .setLicenseUri(MainActivity.https(drm.getString("licenseServerUrl")))
            .setForceDefaultLicenseUri(true).setLicenseRequestHeaders(MainActivity.authorization(drm)).build()
        return OfflineLicenseHelper.newWidevineInstance(config, upstream, DrmSessionEventListener.EventDispatcher())
    }
    private fun validate(session: JSONObject) {
        require(session.getInt("contractVersion") == 1 && session.getString("playbackMode") == "offline")
        require(session.getString("drmSystem") == "widevine" && session.getString("contentType") == "vod")
        require(session.getJSONObject("policy").getBoolean("persistent"))
    }
    private fun keySet(contentId: String): ByteArray = Base64.decode(storage.getString(contentId, null)
        ?: error("No device-bound license for this content"), Base64.NO_WRAP)
    private fun save(contentId: String, id: ByteArray) {
        check(storage.edit().putString(contentId, Base64.encodeToString(id, Base64.NO_WRAP)).commit())
    }
}

```



この低レベルのヘルパーを呼び出す前に、新しいバックエンドの応答が要求されるマッチであることを検証します。 Content ID、操作、署名付きユーザー/デバイスおよび承認の妥当性期間。 安定したオフラインを主張するLicenseIdおよび信頼できるマニフェスト/トラックメタデータをキーセットと一緒に ID、トークンは絶対にありません。 ワーカーの呼び出しをワーカースレッドにブロックする実行。 ライセンス HTTP ヘッダの使用 `setLicenseRequestHeaders` とりあえず DRM 構成; Widevine オプションのキー要求パラメータは異なるマップです。



新しいインテグレーションは、[AndroidX Media3 DRMサポート](https://developer.android.com/media/media3/exoplayer/drm)を使用する必要があります。 レガシー`com.google.android.exoplayer2`プロジェクトは[の公式マイグレーションガイド](https://developer.android.com/media/media3/exoplayer/migration-guide)に従うべきです。 レガシーとAndroidXクラスをミックスしたり、古いバージョンと変更されていないサンプルコンパイルを仮定したりしないでください。



## 9. チェック、更新、リリース、切り替え言語[#](#lifecycle-and-offline-behavior)



これらは別のアクションです。 アクティブなオフライン DRM セッションがリリースされた後、一度に 1 つを実行します。: DrmXPlayer で `player.stopForOfflineLicenseOperation { /* one operation below */ }` を使うか、既存のネイティブ Media3 プレーヤー を最初にリリースします。 再生中にライセンスをチェックしないでください。 期限切れのライセンスの更新を防ぐためのチェックはオプションです。



```
offlineManager.checkLicense(contentId) { result ->
    result.onSuccess { license ->
        if (license.state == DrmXOfflineLicenseState.EXPIRED) {
            // Require renewal online before offering offline playback.
        }
    }
}
offlineManager.renewLicense(offlineEndpoint, contentId, capabilities, customerHeaders) {
    /* update duration and UI after success; show retry on failure */
}
// Only when the customer requests deletion/release:
offlineManager.releaseLicense(offlineEndpoint, contentId, capabilities, customerHeaders) {
    /* after success, remove the corresponding encrypted download */
}
```



`getStoredLicense` はメタデータを読み込みます。`checkLicense` は、権限のあるローカル CDM の期間をクエリします。更新/リリースには、接続とバックエンドから新しい操作固有の認証が必要です。メディアを削除しても、ライセンスはリリースされません。接続が解除されると、サーバーが確認したことを報告するのではなく、次の接続のキューリリースがキューされます。所有者が破壊されると、管理者を閉じます。



```
// Use player.media3Player for the DRM-X wrapper; nativePlayer for plain Media3.
nativePlayer.trackSelectionParameters = nativePlayer.trackSelectionParameters
    .buildUpon()
    .setPreferredAudioLanguages("en") // Chinese: "zh-Hans", "zh"
    .setPreferredTextLanguages("en")  // choose a packaged/downloaded language
    .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false) // true = subtitles Off
    .build()
```



実際の利用可能なトラックから選択をビルドし、利用可能な選択肢を無効にします。 優先順位は、パッケージから不在な英語トラックを作成することはできません。 オフラインユーザーは、ダウンロードした言語のみを選択できます。 画面の回転中にライセンス/キャッシュ状態を保存します。 フルスクリーンは、別のライセンスを取得する理由ではなく、ビュー/ウィンドウの変更です。



## 10. オフライン認可エンドポイント[#](#offline-backend-contract) を実行



アプリの投稿 `contentId`, `contentType: "vod"`, `drmSystem: "widevine"`, `playbackMode: "offline"`, `operation: "acquire" | "renew" | "release"`, `offlineLicenseId` (初期取得のためのnull) と真正な `platformCapabilities`. 更新/リリース ID が認証されたユーザー、デバイス、コンテンツに属していることを確認します。 ポリシーのサーバーサイドを選択し、新しい DRM License Token を発行し、マッチング契約を返します。



```
{
  "contractVersion": 1, "playbackMode": "offline",
  "offlineLicenseId": "YOUR-STABLE-USER-DEVICE-CONTENT-RECORD",
  "contentId": "YOUR-PUBLISHED-CONTENT-ID", "contentType": "vod",
  "drmSystem": "widevine",
  "manifestUrl": "https://media.customer.example/title/manifest.mpd",
  "authorizationExpiresAt": "YOUR-FRESH-UTC-EXPIRY",
  "drm": {
    "system": "widevine",
    "licenseServerUrl": "https://license6.drm-x.com/v1/licenses/widevine",
    "integrationMode": "direct-token",
    "drmLicenseToken": "SHORT-LIVED-OPERATION-AUTHORIZATION",
    "tokenType": "Bearer"
  },
  "policy": {
    "persistent": true, "renewAllowed": true, "maximumHeight": 1080,
    "allowedTrackTypes": ["AUDIO", "SD", "HD"],
    "licenseDurationSeconds": 604800, "rentalDurationSeconds": 259200,
    "playbackDurationSeconds": 172800, "renewalDurationSeconds": 604800
  }
}
```



これらは、再利用可能なライセンス応答ではなく、説明的なプレースホルダーです。 実際の操作から新鮮な値を返す。 SDK は、`drm.authorization` の区切り文字で `integrationMode: "token-proxy"` もサポートしています。 `Cache-Control: no-store` を返します。 成功した認証機関をログに記録しません。 ダウンロード可能な SDK の `OFFLINE_INTEGRATION.md` と PHP のサンプルのオフラインエンドポイントを具体的なリクエスト処理を参照してください。



## 11. デバイスとトラブルシューティングのテスト[#](#device-qualification)



### Widevine のプロビジョニングと中国ネットワークのテスト



コンテンツライセンス取得前のデバイス証明書のプロビジョニング。 プレビュー9から電話デモが送信 CDM認証された顧客による署名された要求 HTTPS Googleへの中継。署名された応答は変更されません。これは、電話がGoogleのプロビジョニングサービスに直接到達できない場合に役立ちます。顧客リレーはまだ到達可能でなければなりません。



2.0 のプロビジョニングは、工場のキーボックスを使用します。 4.0 のプロビジョニングは、異なるデバイス認証モデルを使用します。 これらのバージョンは別々です。 L1/L3 セキュリティレベル、および プレーヤー それらを切り替えません。 既に規定されている L3 電話は、別の電話がデバイスプロビジョニングを待ちながら再生することができます。 ここでレビューされた公共の公式のソースは、中国が唯一の2.0をサポートすることを確立しません。 参照 [NXP's デバイスの統合ガイド](https://www.nxp.com/docs/en/user-guide/UG10158.pdf) そして、 [Android's の暫定実装](https://android.googlesource.com/platform/packages/apps/RemoteProvisioner/+/refs/tags/android-13.0.0_r15/src/com/android/remoteprovisioner/WidevineProvisioner.java).



Preview.10 エラーレポートは、直接または中継輸送、タイムアウト/DNS/TLS/HTTP の失敗、HTTP のステータスと経過時刻を区別します。`provisioningModel: BootCertificateChain` は AOSP 4.0 モデルを表します。`unknown` または `other` は 2.0 を確立しません。 PHP ログは、上流 Google の失敗から拒否された入力を区別します。 レポートは、署名されたペイロード、URL、認証情報 および一意のデバイス識別子を除外します。



所定の場所にあるAPKを更新し、ストリーミングを再試行し、影響を受けたネットワーク上の1つのダウンロードを繰り返して、コピーログと近似時間を保持します。ダウンロードとライセンスを既存の状態に保ちます。 リレーは、デバイス/Googleによって拒否された未使用のWidevineまたは認証情報を修復することはできません。 影響を受けた電話の実質的な受諾が必要です。



プレビュー8から、ネイティブ L2/L3 または未知のレベル Widevine 再生は制限されます SD 解読前の(576pまで)。 元のフォワード Android バックエンドによる機能:セッション API 狭いソフトウェア Widevine 複数のキーのライセンスをオーディオ/SD キー。。 PHP サンプルは、これらのフィールドを保存し、オフラインのリクエストもキャップします。 SD ソフトウェアの再構築とポリシーの許可 DRM 必要な; ハードウェアのみおよび均一な単一キーのセキュリティ ルールは変更されません。 設定は、ビデオの調光なしで半透明パネルを使用します。 数値 `cryptoErrorCode` 安全なエラーレポートに含まれています。



| 試験・症状 | 期待される行動/次のチェック | 
| --- | --- |

| SDK ダウンロードは403を返します | アクティブなプロフェッショナルまたはエンタープライズサブスクリプションで組織を選択します。 トライアル後の計画的なアップグレードは、SDK のロックを解除しません。 | 

| セッション 401 / 403 | ログイン、エンタイトル、環境、公開されたContent IDおよびポリシーを確認してください。 認証を迂回しないでください。 | 

| オフラインは失敗しますが、オンライン作品 | CDM の長さ、ダウンロード完了、キャッシュの場所、ストリームキー、および必要なすべてのライセンスキーを確認します。 | 

| 英語 / 中国欠落 | パッケージ言語のメタデータ、実際のトラックおよびオフライン言語選択を点検して下さい。 | 

| L1、HDCPまたはデコーダーの失敗 | 物理的なハードウェア、プロビジョニング、コーデックおよび出力要件を確認してください。 機能文字列は認証されません。 | 

| 再起動、回転、アップグレード | 永続的なライセンスとキャッシュは、重複取得せずにサポートされたライフサイクルの変更を存続します。 バックアップは、ライセンスを新しいデバイスに移行してはいけません。 | 

| 飛行機モード | Wi-Fi とモバイルデータを無効化し、アプリを再起動し、ローカルで再生し、約束された言語を切り替えます。 | 

| 有効期限、貯蔵圧力、アカウント スイッチ | 未使用/更新状態を表示し、不完全なメディアを回復し、別のユーザーのオフラインカタログを漏らさずにユーザー/デバイスアクセスを強化します。 | 


サポートされている電話、タブレット、それぞれを修飾します。 Android TV/Google TV, Fire TV または埋め込まれる Android モデル Widevine、安全な解読、コーデック/決断、 HDCP、外的な表示、クロック変更および長い切断。 ないすべて Android デバイスは同じを持っています DRM 機能。ハリウッドのコンテンツは、権利者の保護とデバイス要件を要求します。, だけでなく、成功したデコード.



**切断前に取得する機会はありませんか?**これは、異なる展開です: 資格のあるオンボードライセンスサービスの問題は、旅行中にローカルネットワーク上のライセンスを発行します。この SDK を使用することだけでは有効ではありません。 DRM-X は、プロバイダーの許可、プロビジョニング、キー保護、および完全に接続されていない受諾テストをカバーするカスタムデプロイメントであなたと一緒に作業することができます。 参照 [航空機、バス、列車、船舶のオンボード DRM](https://multi-drm.drm-x.com/ja/solutions/on-board-drm).



## Universal Player プレイリストと共有制御[#](#universal-player-playlists-and-shared-controls)



Android の preview.11 以降の評価アプリと現在の Android/Apple 開発版ソースには、動画内の「前へ・再生/一時停止・次へ」、CC、設定、全画面表示の共通コントロールがあります。再生中は 2 秒後に非表示になります。Android のプレイリストメニューから「Play next」は削除されています。選択したシャッフル/リピート設定は強調表示され、再起動後も保持されます。両方をオフにすると、保存された順序で再生し、最後の動画で停止します。ドラッグハンドルで現在の動画を再起動せずに並べ替えられ、保存された順序はダウンロード済みのプレイリストにも適用されます。これらの最新ソース APIs は、Console の preview.10 SDK バンドルにはまだ含まれていません。



プレイリストを設定する前に、同じ顧客/ビューア `storageNamespace` と `DrmXPlayer` に `playerView.storageNamespace` を供給します。 VOD の進捗状況は、定期的に保存され、一時停止/停止し、ストリーミングまたはダウンロードした再生中にその Content ID のために復元されます。 ライブ再生は保存された VOD 位置を使用しません。 完了したダウンロードは、ネットワークメディアソースなしで同じシャッフル/受信/再開動作を保持します。 このデバイスローカル状態は、デバイス間の進捗状況を同期しません。



Web版とAndroid版は、コンパクトなブルーコントロールを備えた一貫性のあるダーク設定パネルを使用します。プレイリストは、次の/前のサポート、シャッフル、1/allを繰り返し、そしてcancellable自動進歩をサポートします。すべてのアイテムは、新しい認定再生リクエストを使用します。チャプターとトランスクリプトのメタデータは、コンテンツカタログから提供されます。



再生速度、スリープタイマー、キャプション、許可された品質、オーディオ言語、セッションブックマーク、A-Bループが利用可能です。 Webは、キーボードショートカット、オプションビューアスコープのローカル履歴書、トランスクリプト検索、およびサポートされているブラウザPiPを追加します。 Androidは、ネイティブWidevineオフラインのダウンロードと更新を保持します。 鋳造およびクロスデバイスの進捗は、別のアプリケーション統合を必要とします。



### 専門および企業ロゴの設定



ブランディングは、ビデオ表面を離れます。 認定ロゴ、製品情報、プレーヤーバージョンの「設定」→「約プレーヤー」を開きます。 Webは、この情報を右クリックで提供します。 公衆電話のデモには、WayMaker、GodのGoodness、Fresh Oil、および4K Test Videoが含まれます。 すべての選択は、独自の承認を受け取ります。



`{"mode":"hidden"}` または `{"mode":"custom","logoUrl":"https://your-site.example/logo.png","label":"Your brand"}` に信頼できるバックエンドに `playerBranding` を設定します。このプラットフォームは、アクティブなプロフェッショナルまたはエンタープライズサブスクリプションをチェックします。その他のプランは `{"mode":"default"}` を使用します。無許可のカスタム/ヒデンリクエストは 403 を返します。これはセキュリティの透かしとは別です。



PHPのダウンロードには、編集可能なプレイリストとブランディング設定が含まれています。Androidの顧客ZIPには、プレイリストのコールバックの例が含まれています。 バックエンドでプレイリストの許可を保ち、APIクライアント認証情報をプレーヤーのいずれかに埋め込むことはありません。

<!-- Canonical: https://docs.drm-x.com/ja/sdk/android -->
<!-- Language: ja -->
