Skip to content

Migrate Media I guide snippets - #1115

Draft
barbaralaw wants to merge 9 commits into
android:mainfrom
StellarElements:media-snippets-migration
Draft

barbaralaw wants to merge 9 commits into
android:mainfrom
StellarElements:media-snippets-migration

Conversation

@barbaralaw

@barbaralaw barbaralaw commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Extracts the Kotlin samples from five Media I guides (editing, playback, Android TV, media controls and audio focus) into :media as region-tagged source, so the guides can import them instead of hardcoding them.

18 new region tags across 5 pages. The Android TV page reuses playback-app's create_media_session region because it prints the same block. Of the 20 snippets the pages import, 9 publish exactly what the page shows. The rest differ for a reason listed below.

Region tags all begin android_media_; the lists and tables drop that prefix.

Code snippets are for:

List of modifications

  • Editing-app page: editing_set_hdr_mode: the closing ) of ImmutableList.of(videoSequence) moves to its own line (formatter).
  • Editing-app page: editing_trim_video and editing_custom_effects: 3 comments end with a full stop.
  • Editing-app page: editing_preview_audio_effects: drops the enableOffload parameter and .setOffloadMode(...), which current Media3 no longer has, so the published code doesn't compile. The builder is re-wrapped (formatter).
  • Playback-app page: playback_app_playback_service and playback_app_on_get_session: 3 comments end with a full stop.
  • Playback-app page: playback_app_connect_ui: adds super.onStart() as the first line of onStart(), because an Activity that overrides onStart() must call it or the app crashes. Indentation goes from 2 to 4 spaces (formatter).
  • Android-tv page: no visible change. The page's MediaSession block is identical to the Playback-app page's, so it imports playback_app_create_media_session.
  • Mobile page: surfaces_mobile_custom_command_buttons: indentation goes from 2 to 4 spaces, and the under-indented onConnect body is aligned with its function (formatter). 1 comment ends with a full stop.
  • Audio-focus page: audio_focus_request: the setAudioAttributes(...) argument moves onto its own lines (formatter). 5 comments end with a full stop.
  • Audio-focus page: audio_focus_request_pre_o: adds the missing : in lateinit var afChangeListener: AudioManager.OnAudioFocusChangeListener, so the code compiles. The bare ... becomes // ..., and the requestAudioFocus arguments are indented 4 instead of 8 (formatter). 2 comments end with a full stop.
  • Audio-focus page: audio_focus_change_listener: 7 comments end with a full stop.

The per-page tables below give the same changes with the page section for each snippet.

Scope after the Media legacy assessment

This PR was paused while the team decided which Media pages are legacy. Following that assessment:

  • Google Assistant and media apps is legacy and stays hardcoded. Its snippets are removed from this PR.
  • Media controls: only Customize command buttons is migrated. Everything under Using the legacy media APIs (standard actions, slot reservation extras, custom actions, PlaybackState callback, onGetRoot and the pre-Android 13 notification) stays hardcoded.
  • The androidx-media dependency is removed, since only the legacy snippets needed it.

How to read the "why" column

Why What a reader of the page would see
ktlint wrap Same tokens, different line breaks or indentation. spotlessApply normalizes indentation to 4 spaces and wraps long calls.
// ... The page prints a bare ..., which Kotlin will not compile. The region publishes // ....
does not compile The published Kotlin does not compile against this module's dependencies, and it isn't a sample meant to show an error. The region publishes the corrected code.
crash fix The published code compiles but crashes at runtime. The region publishes the fix.
full stop Natural-language comments inside a region end with a full stop, for consistency. 21 comments in this PR, listed per snippet.

Per page

1. Create a basic video editing app using Media3 Transformer

https://developer.android.com/media/implement/editing-app

8 snippets, 4 match, 4 differ.

Snippet Page section Difference Why
editing_set_hdr_mode Set HDR mode Closing ) of ImmutableList.of(videoSequence) on its own line ktlint wrap
editing_trim_video Trim a video 2 trailing comments get a full stop full stop
editing_custom_effects Create custom effects 1 comment gets a full stop full stop
editing_preview_audio_effects Preview effects Drops the enableOffload parameter from buildAudioSink and .setOffloadMode(...) from DefaultAudioSink.Builder; the builder is rewrapped does not compile, ktlint wrap

Matching as published: editing_transcode, editing_built_in_effects, editing_preview_effects, editing_start_transformation.

2. Create a basic media player app using Media3 ExoPlayer

https://developer.android.com/media/implement/playback-app

5 snippets (4 new, plus the existing playback_app_create_exoplayer), 2 match, 3 differ.

Snippet Page section Difference Why
playback_app_playback_service Implementing a MediaSessionService 2 comments get a full stop full stop
playback_app_on_get_session Implementing a MediaSessionService 1 comment gets a full stop full stop
playback_app_connect_ui Connecting to your UI Adds super.onStart() as the first line of onStart(). Indentation 2 → 4 spaces crash fix, ktlint wrap

Matching as published: playback_app_create_exoplayer, playback_app_create_media_session.

playback_app_on_get_session sits inside playback_app_playback_service in source. The outer region hides it with a silent exclude so each tag is opened only once, and both render as the page shows.

3. Extend your media app to Android TV

https://developer.android.com/media/implement/surfaces/android-tv

1 snippet, matches as published. The page's MediaSession block is identical to playback-app's, so the page imports playback_app_create_media_session instead of a duplicate region.

4. Media controls

https://developer.android.com/media/implement/surfaces/mobile

1 snippet migrated; the legacy section stays hardcoded (see Scope above).

Snippet Page section Difference Why
surfaces_mobile_custom_command_buttons Customize command buttons Indentation 2 → 4 spaces, and the under-indented onConnect body is indented to match its function; 1 comment gets a full stop ktlint wrap, full stop

The rendered class name stays PlaybackService, as on the page. The region sits inside a private object CustomControlsSnippet wrapper (outside the tags) to avoid clashing with playback-app's PlaybackService.

5. Manage audio focus

https://developer.android.com/media/optimize/audio-focus

5 snippets, 2 match, 3 differ.

Snippet Page section Difference Why
audio_focus_request Audio focus in Android 8.0 through Android 11 The setAudioAttributes(...) argument moves onto its own lines; 5 comments get a full stop ktlint wrap, full stop
audio_focus_request_pre_o Audio focus in Android 7.1 and lower lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener gets its missing :; ... → // ...; requestAudioFocus arguments indented 4 instead of 8; 2 comments get a full stop does not compile, // ..., ktlint wrap, full stop
audio_focus_change_listener Responding to an audio focus change 7 comments get a full stop full stop

Matching as published: audio_focus_abandon, audio_focus_delayed_stop_runnable.

The page shows the request setup and onAudioFocusChange as one continuous block, but in source the override has to live on a type that implements AudioManager.OnAudioFocusChangeListener (private inner class AudioFocusHandler). audio_focus_request is one region, and a silent exclude hides the end of the setup function and the inner-class header between the two parts. The hidden } and { balance each other, so the rendered block is exactly the page's block (plus the changes listed above).

Snippets not migrated

  • Editing-app page: the Kotlin and Groovy blocks under Get started, because they are dependency blocks.
  • Playback-app page: the Kotlin and Groovy blocks under Getting started and Managing playback with a media session, because they are dependency blocks. The MediaSessionService and MediaLibraryService blocks, because they are in the Android manifest file.
  • Mobile page: everything under Using the legacy media APIs (6 Kotlin blocks and their Java versions), because those samples use the legacy media APIs and the section stays as it is.

The Java versions of the migrated Kotlin samples aren't migrated. The paired guide-page change removes them, following Kotlin-first guidance.

Other Media I pages not in this PR:

Page Verdict
Google Assistant and media apps Legacy: stays hardcoded
Best practices for sharing video Excluded: the one Kotlin block uses Media3 APIs that no longer exist, and correcting it needs a matching prose change. Tracked for a combined code + prose fix.
Android for Cars Keep hardcoded: the page has only XML blocks (manifest entries and XML resources)
Media3 Inspector Keep hardcoded: the index page has only a Gradle block; its subpages already use code macros

Dependencies

One entry added to gradle/libs.versions.toml: androidx-media3-transformer, which reuses the existing media3 version through version.ref.

:media now also depends on media3-common, media3-effect, media3-session, media3-transformer, media3-ui and guava-android, each used by a snippet in this PR.

Live snippet defects this surfaced

Fixed in the migrated regions:

  1. Audio focus (pre-O): lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener is missing :.
  2. Editing preview audio: the live buildAudioSink still takes enableOffload and calls DefaultAudioSink.Builder.setOffloadMode. Neither exists in this module's Media3. Offload is now configured with TrackSelectionParameters.AudioOffloadPreferences, which doesn't belong in this processor-preview sample, so the region publishes the current three-argument override.
  3. Playback app, connect UI: onStart() never calls super.onStart(), so the activity throws SuperNotCalledException. The region adds the call.

On pages or sections that stay hardcoded (not fixed here; listed for the page owner):

  1. Media controls, legacy section: each continuation line of the stateActions chain starts with or, so Kotlin ends the statement after ACTION_PLAY and the next lines don't compile.
  2. Media controls, legacy section: the object: MediaSession.Callback() sample is the platform android.media.session callback, but session in that section is a MediaSessionCompat, whose setCallback takes a MediaSessionCompat.Callback.
  3. Media controls, legacy section: onGetRoot starts with ... and never closes the function }.
  4. Sharing HDR: Transformer.Builder.setTransformationRequest() and TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR no longer exist in this module's Media3. The successor is Composition.Builder.setHdrMode(HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL), but porting the block would change what the page teaches while the prose still describes the old flow.
  5. Sharing, B-frames and encoding profiles: the three Java MediaFormat blocks call format.setInt32(...), which isn't a Java API; it should be format.setInteger(...).

Notes for reviewers

  • class PlaybackService in PlaybackApp.kt is public because its declaration line is inside the playback_app_playback_service region; making it private would change the rendered snippet.
  • :media's own AndroidManifest.xml now registers that PlaybackService, the way an app would: exported, with the androidx.media3.session.MediaSessionService intent filter, foregroundServiceType="mediaPlayback", and the FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PLAYBACK permissions (required from target SDK 31 and 34). It's module configuration, not a snippet; the page's own manifest block stays hardcoded.
  • In custom_command_buttons, the required onGetSession override sits inside the class, hidden with a silent exclude. The compile-only saveToFavorites(...) stub is a private top-level function outside the region.

Verification

Rebased on main (media3 1.11.1).

  • ./gradlew :media:spotlessCheck passes
  • ./gradlew :media:compileDebugKotlin passes
  • ./gradlew :media:lintDebug passes
  • Every region was rendered with DevSite's includecode region parser and compared with the live page block; the differences are exactly the ones listed above.

@barbaralaw
barbaralaw marked this pull request as ready for review September 18, 2026 15:17
@snippet-bot

snippet-bot Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Here is the summary of changes.

You are about to add 32 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

* limitations under the License.
*/

package com.example.media

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have android in the package name for these files? com.example.android.media?

Comment thread gradle/libs.versions.toml Outdated
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle-compose" }
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-lifecycle-viewmodel-navigation3" }
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core" }
androidx-media = { module = "androidx.media:media", version.ref = "androidx-media" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should add this dependency or not (if this is the old version of the media library). Posted a question on our group chat, curious for your thoughts!


@OptIn(UnstableApi::class)
// [START android_media_surfaces_mobile_custom_command_buttons]
class CustomControlsPlaybackService : MediaSessionService() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we keep the rendered class name as class PlaybackService : MediaSessionService() inside the region tag and avoid the collision by wrapping the snippet in a private object outside the region tag?
```kotlin
private object CustomControlsSnippet {
// [START android_media_surfaces_mobile_custom_command_buttons]
class PlaybackService : MediaSessionService() {
...
}
// [END android_media_surfaces_mobile_custom_command_buttons]
}

@barbaralaw
barbaralaw marked this pull request as draft September 30, 2026 20:57
@barbaralaw

Copy link
Copy Markdown
Contributor Author

Moving this back to draft while I apply the outcome of the Media legacy assessment and get an internal review. I'll address your comments and re-request your review once it's ready. Thanks for your patience!

kkuan2011 and others added 8 commits September 30, 2026 21:05
- Remove the Assistant, pre-Android 13 and legacy mobile surfaces
  snippets. Those pages and sections stay hardcoded on DAC as legacy.
- Remove the androidx-media dependency. AudioFocus.kt now uses the
  framework MediaController in non-rendered code.
- Keep the rendered class name PlaybackService by wrapping the custom
  command buttons snippet in a private object (review feedback).
- Reuse playback-app's create_media_session region for the Android TV
  page instead of a duplicate region.
- Open audio_focus_request and playback_service once each, using
  silent excludes. Rendered output is unchanged.
- End natural-language comments inside regions with a full stop.
@barbaralaw
barbaralaw force-pushed the media-snippets-migration branch from e770b58 to 6591435 Compare September 30, 2026 21:10
Show super.onStart() in connect_ui, move the saveToFavorites stub out of the region, drop the unused core-ktx dependency, and register PlaybackService in the module manifest.
@barbaralaw barbaralaw changed the title Media I snippets migration Migrate Media I guide snippets Oct 2, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants