StorytellerClipsFragment is a fragment that can be used to embed a clip in your own activities.
Like a regular Android Fragment, it can be configured from an XML layout or instantiated
programmatically and attached to the host's (another fragment or activity) FragmentManager via a
fragment transaction.
Embedded Clips fill the host container. Size the container according to your app's layout and keep
that size stable during refreshes to avoid layout shift. If the content and container dimensions
differ, cropping may be applied to avoid distortion.
StorytellerClipsFragmentcan be used directly from the layout XML files. The usage is identical to
typical Android fragments.
You need to specify the fully qualified fragment class name in the android:name property and the
collection ID using app:storyteller_collection_id_property. Note that in this example, a constraint
is applied to reserve stable space for the embedded player. app:storyteller_initial_category is
optional and can be used to set the initial category of the collection to be viewed. If this is used. The clip will be first loaded with the collection.
The selected category will then be navigated to automatically.
StorytellerClipsFragment can also be used programmatically. To embed a clip fragment
programmatically, you need to follow these steps:
Create a new fragment instance using the StorytellerClipsFragment.create(collectionId: String, context: StorytellerAnalyticsContext)
method.
a. Optionally, you can also set the initialCategory property to a string value to automatically navigate to a selected category.
Create a fragment transaction that would add this fragment to the fragment container view.
Commit the created transaction.
See the following snippet illustrating attaching a fragment in the Activity.onCreate method:
overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)// An example using viewBinding feature; see view binding for reference.valbinding=ActivityClipFragmentHostBinding.inflate(layoutInflater)setContentView(binding.root)valstorytellerClipsFragment=StorytellerClipsFragment.create(collectionId="yourCollectionId",context=mapOf("placementId"to"embedded_clips","location"to"ClipsScreen"))valtransaction=supportFragmentManager.beginTransaction()transaction.add(binding.fragmentHost.id,storytellerClipsFragment)transaction.commit()}
StorytellerClipsFragment by default starts playback as soon as it is attached to the host and visible. Standard Android Fragment lifecycle events typically handle automatic pausing and resuming (e.g., when the app goes to the background), complementing the manual control provided by the shouldPlay property.
// finding the fragment by id using the fragment managervalstorytellerClipsFragment=supportFragmentManager.findFragmentById(binding.fragmentHost.id)asStorytellerClipsFragmentstorytellerClipsFragment.shouldPlay=false// stops playbackstorytellerClipsFragment.shouldPlay=true// starts playback
StorytellerClipsFragment also contains canGoBack:Boolean property which can be used to check if the fragment can go back from the current Category or is it at the top level.
StorytellerClipsFragment also contains listener property which can be used to control playback and handle top level back button press.
Data-load callbacks are emitted for the top-level Clips collection load. Following feed loads do not trigger onDataLoadStarted or onDataLoadComplete, and Embedded Clips completion does not include dataCount.
valstorytellerClipsFragment=StorytellerClipsFragment.create(collection="yourCollectionId",topLevelBackEnabled=true,context=mapOf("placementId"to"embedded_clips","location"to"ClipsScreen"))storytellerClipsFragment.listener=object:StorytellerClipsFragment.Listener{overridefunonTopLevelBackPressed():Boolean{// will be invoked when top level back button is pressedreturntrue// true if fragment needs to be stopped / host should handle back}overridefunonDataLoadStarted(){// will be invoked when data load starts}overridefunonDataLoadComplete(success:Boolean,error:Error?){// will be invoked when data load completes or fails}}storytellerClipsFragment.reloadData()
StorytellerClipsFragment does not show the top level back button by default. You can control the behaviour by setting topLevelBackEnabled property and setting onTopLevelBackPressed according to your needs.
valstorytellerClipsFragment=StorytellerClipsFragment.create(collection="yourCollectionId",topLevelBackEnabled=true,context=mapOf("placementId"to"embedded_clips","location"to"ClipsScreen"))storytellerClipsFragment.listener=object:StorytellerClipsFragment.Listener{overridefunonTopLevelBackPressed():Boolean{// will be invoked when top level back button is pressedreturntrue// true if fragment needs to be stopped}}
StorytellerClipsFragment also contains canGoBack:Boolean property which can be used to check if the fragment can go back from the current Category or is it at the top level.
If the category is found in the collection, the clip will be loaded with the collection and the selected category.
The category will be navigated to automatically. If the category is not found in the collection or is invalid, it will be ignored.
Embedded Clips supports per-presentation ad control through StorytellerClipsAdConfiguration.
Embedded bottom banners are opt-in across every embedding entry point — rememberStorytellerEmbeddedClipsState, StorytellerEmbeddedClipsState, and both StorytellerClipsFragment.create(...) overloads. Pass an adConfiguration — directly, or via StorytellerClipCollectionConfiguration.adConfiguration — to enable the banner. A StorytellerClipCollectionConfiguration with no adConfiguration (null) keeps the bottom banner disabled for embedding; full-screen Storyteller.openCollection enables it when the configuration is omitted.
Embedded Clips continue to suppress the standard zero-index opening ad-as-Clip. When a correctly configured StorytellerImaModule is registered, preRollEnabled = true permits one true IMA pre-roll before the initially opened Embedded Clip; set it to false for a user who should not receive that pre-roll. betweenClipsAdProviderOrder controls the ordered allowlist for later standard ad slots, while bottomBannerEnabled controls only bottom banners. Nullable frequency and initialIndex values independently inherit remote cadence; valid supplied values override later cadence for this Embedded presentation. See Per-presentation Clips Ad Controls.
StorytellerClipsFragment in order to reload data you can call reloadData() method. This method will make a request to the
backend to fetch the latest data.
If this method is called when there are category filters applied, then it will go back one level. If there are no category filters applied this method will reload data.
Loading state can be observed by setting listener property and overriding it's onDataLoadStart and onDataLoadComplete methods.
These loading callbacks apply to the top-level Clips collection load. Following feed loads do not trigger Embedded Clips loading callbacks.
valstorytellerClipsFragment=StorytellerClipsFragment.create(collection="yourCollectionId",topLevelBackEnabled=true,context=mapOf("placementId"to"embedded_clips","location"to"ClipsScreen"))storytellerClipsFragment.listener=object:StorytellerClipsFragment.Listener{overridefunonTopLevelBackPressed():Boolean{// will be invoked when top level back button is pressedreturntrue// true if fragment needs to be stopped / host should handle back}overridefunonDataLoadStarted(){// will be invoked when data load starts}overridefunonDataLoadComplete(success:Boolean,error:Error?){// will be invoked when data load completes or fails}}storytellerClipsFragment.reloadData()
Use topInset and bottomInset only when the Embedded Clips container is deliberately laid behind
an occluding system surface. For example, topInset can offset the title and top controls when the
container extends behind the status bar. If the host has already consumed that system inset by
padding or sizing the container, leave the corresponding Storyteller inset at 0 so it is not
applied twice.
The Clips Instructions screen uses these same insets for its content. The Tap to Start
button stays visible without scrolling, while the heading and instruction rows scroll above
it on compact layouts. This applies to both Compose Embedded Clips and StorytellerClipsFragment.
These properties are not the integration seam for sibling host UI such as a clickable app bottom
navigation bar. Size the Embedded Clips container so its bottom edge ends at the top of that
navigation instead; see Host navigation and bottom banners.
Embedded Clips fill the bounds supplied by the host. When the screen also contains clickable bottom
navigation, make the Storyteller container end at the navigation's top edge. Storyteller owns layout
inside those local bounds; the host owns the navigation component and system bars. From Android SDK
11.7.0, effective-bottom Embedded Clips use a compact 8dp progress region matching the track's
maximum visual height; no larger scrub target is reserved for this arrangement. When a
width-constrained video creates enough measured black space below the media for the banner and its
16dp lower band, the progress track uses its top edge at the
approved position 16dp before the banner. Without qualifying bottom letterbox space, the current banner-before-progress order
is preserved and the bottom-edge track remains at the local container boundary. Caller navigation and
system navigation remain outside the measured letterbox, so do not add their heights as SDK padding.
Clips without a banner, direct/modal Clips, and eligible Embedded ABOVE_ACTION layouts retain the
existing 80dp target. A height-constrained host retains the legacy layout rather than hiding an
eligible banner.
Do not add the host navigation height to StorytellerEmbeddedClipsState.bottomInset or
StorytellerClipsFragment.bottomInset after excluding it from the container. Doing both creates
double padding.
Apply gesture-navigation or three-button system insets to the host navigation or its parent exactly
once. Keep bottomInset = 0 when those system bounds are already outside the fragment container.
StorytellerClipsFragment can be used in Jetpack Compose using the StorytellerEmbeddedClips composable.
You can optionally pass clipId to start the embedded player from a specific Clip in the collection.
This is useful when the host app has a Storyteller Clip ID for a selected content item, such as a
live-blog post or match-centre event.
classDemoComposeEmbeddedClipsActivity:FragmentActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)valcollection=intent.getStringExtra("collection")?:""valwidthPercentage=intent.getIntExtra("width",100)/100FvalheightPercentage=intent.getIntExtra("height",80)/100FvaltopLevelBack=intent.getBooleanExtra("topLevelBack",false)valinitialCategory=intent.getStringExtra("category")?:""enableEdgeToEdge()setContent{valstate=rememberStorytellerEmbeddedClipsState(collectionId=collection,topLevelBack=topLevelBack,initialCategory=initialCategory,context=mapOf("placementId"to"embedded_clips_compose","location"to"ClipsScreen"))StorytellerEmbeddedClips(modifier=Modifier.fillMaxWidth(widthPercentage).fillMaxHeight(heightPercentage),state=state,)}}companionobject{funstart(context:Context,collection:String,category:String?,width:Int,height:Int,topLevelBack:Boolean){// add params to intentIntent(context,DemoComposeEmbeddedClipsActivity::class.java).apply{putExtra("collection",collection)putExtra("category",intialCategory)putExtra("width",width)putExtra("height",height)putExtra("topLevelBack",topLevelBack)context.startActivity(this)}}}}
rememberStorytellerEmbeddedClipsState is a composable function that creates a StorytellerEmbeddedClipsState object that holds the state of the StorytellerEmbeddedClips composable.
It accepts an optional clipId. When provided, Embedded Clips starts playback from that Clip if it
is available in the collection.
StorytellerEmbeddedClipsState contains canGoBack property that can be used to check if the fragment can go back from the current Category or is it at the top level.
valstate=rememberStorytellerEmbeddedClipsState(collectionId="collection",topLevelBack=topLevelBack,context=mapOf("placementId"to"embedded_clips_compose","location"to"ClipsScreen"))valcanGoBack=state.canGoBack// true if user can navigate back
StorytellerEmbeddedClipsState contains goBack() which will move the content to previous Category if the user is not at the top level.
valstate=rememberStorytellerEmbeddedClipsState(collectionId=collection,topLevelBack=topLevelBack,context=mapOf("placementId"to"embedded_clips_compose","location"to"ClipsScreen"))state.goBack()// navigate to previous category programmatically
{"slug": "storyteller-clips-fragment", "page_title": "Embedded Clips", "page_url": "StorytellerClipsFragment/", "canonical_url": "/android/StorytellerClipsFragment/", "markdown": "# StorytellerClipsFragment\n\n`StorytellerClipsFragment` is a fragment that can be used to embed a clip in your own activities.\nLike a regular Android Fragment, it can be configured from an XML layout or instantiated\nprogrammatically and attached to the host's (another fragment or activity) FragmentManager via a\nfragment transaction.\n\n> Embedded Clips fill the host container. Size the container according to your app's layout and keep\n> that size stable during refreshes to avoid layout shift. If the content and container dimensions\n> differ, cropping may be applied to avoid distortion.\n\n## Showcase examples\n\n- [Compose \u2014 embedded clips (`MomentsScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/moments/MomentsScreen.kt#L160)\n- [XML \u2014 embedded clips (`EmbeddedClipFragment`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/embedded/EmbeddedClipFragment.kt#L74)\n\n## Using StorytellerClipsFragment from xml layout\n\n`StorytellerClipsFragment`can be used directly from the layout XML files. The usage is identical to\ntypical Android fragments.\nYou need to specify the fully qualified fragment class name in the android:name property and the\ncollection ID using app:storyteller_collection_id_property. Note that in this example, a constraint\nis applied to reserve stable space for the embedded player. app:storyteller_initial_category is\noptional and can be used to set the initial category of the collection to be viewed. If this is used. The clip will be first loaded with the collection.\nThe selected category will then be navigated to automatically.\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-auto\" xmlns:tools=\"http://schemas.android.com/tools\"\n android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n\n <androidx.fragment.app.FragmentContainerView\n android:id=\"@+id/fragment_host\"\n android:name=\"com.storyteller.ui.pager.StorytellerClipsFragment\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"400dp\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\"\n app:layout_constraintTop_toTopOf=\"parent\"\n app:storyteller_collection_id=\"your-collection-id\"\n app:storyteller_initial_category=\"initial-category-of-collection\"/>\n\n<\/androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n## Using StorytellerClipsFragment programmatically\n\n`StorytellerClipsFragment` can also be used programmatically. To embed a clip fragment\nprogrammatically, you need to follow these steps:\n\n1. Create a new fragment instance using the `StorytellerClipsFragment.create(collectionId: String, context: StorytellerAnalyticsContext)`\n method.\n a. Optionally, you can also set the `initialCategory` property to a string value to automatically navigate to a selected category.\n2. Create a fragment transaction that would add this fragment to the fragment container view.\n3. Commit the created transaction.\n\nSee the following snippet illustrating attaching a fragment in the `Activity.onCreate` method:\n\n```kotlin\noverride fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n // An example using viewBinding feature; see view binding for reference.\n val binding = ActivityClipFragmentHostBinding.inflate(layoutInflater)\n setContentView(binding.root)\n\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collectionId = \"yourCollectionId\",\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n val transaction = supportFragmentManager.beginTransaction()\n transaction.add(\n binding.fragmentHost.id,\n storytellerClipsFragment\n )\n transaction.commit()\n}\n```\n\n## Controlling playback in StorytellerClipsFragment\n\n`StorytellerClipsFragment` by default starts playback as soon as it is attached to the host and visible. Standard Android Fragment lifecycle events typically handle automatic pausing and resuming (e.g., when the app goes to the background), complementing the manual control provided by the `shouldPlay` property.\n\n```kotlin\n// finding the fragment by id using the fragment manager\n val storytellerClipsFragment = supportFragmentManager.findFragmentById(binding.fragmentHost.id)\n as StorytellerClipsFragment\n\nstorytellerClipsFragment.shouldPlay = false // stops playback\nstorytellerClipsFragment.shouldPlay = true // starts playback\n```\n\n`StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level.\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n topLevelBackEnabled = true,\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n val canGoBack = storytellerClipsFragment.canGoBack\n```\n\n`StorytellerClipsFragment` also contains `listener` property which can be used to control playback and handle top level back button press.\nData-load callbacks are emitted for the top-level Clips collection load. Following feed loads do not trigger `onDataLoadStarted` or `onDataLoadComplete`, and Embedded Clips completion does not include `dataCount`.\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n topLevelBackEnabled = true,\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener {\n override fun onTopLevelBackPressed(): Boolean {\n // will be invoked when top level back button is pressed\n return true // true if fragment needs to be stopped / host should handle back\n }\n\n override fun onDataLoadStarted() {\n // will be invoked when data load starts\n }\n\n override fun onDataLoadComplete(success: Boolean, error: Error?) {\n // will be invoked when data load completes or fails\n }\n }\n storytellerClipsFragment.reloadData()\n```\n\n## External back button handling\n\n`StorytellerClipsFragment` does not show the top level back button by default. You can control the behaviour by setting `topLevelBackEnabled` property and setting `onTopLevelBackPressed` according to your needs.\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n topLevelBackEnabled = true,\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener {\n override fun onTopLevelBackPressed(): Boolean {\n // will be invoked when top level back button is pressed\n return true // true if fragment needs to be stopped\n }\n }\n```\n\n`StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level.\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n topLevelBackEnabled = true,\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n val canGoBack = storytellerClipsFragment.canGoBack\n```\n\n## Initial Category\n\nTo set the `StorytellerClipsFragment` to start with a specific category, you can set the `initialCategory` property.\n\nProgrammatical Usage\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n initialCategory = \"yourCategory\",\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n```\n\nXML Usage\n\n```xml\n <androidx.fragment.app.FragmentContainerView android:id=\"@+id/fragment_host\"\n android:name=\"com.storyteller.ui.pager.StorytellerClipsFragment\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"400dp\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\"\n app:layout_constraintTop_toTopOf=\"parent\"\n app:storyteller_collection_id=\"your-collection-id\"\n app:storyteller_initial_category=\"initial-category-of-collection\"/>\n```\n\nIf the category is found in the collection, the clip will be loaded with the collection and the selected category.\nThe category will be navigated to automatically. If the category is not found in the collection or is invalid, it will be ignored.\n\n## Ad Placement Controls\n\nEmbedded Clips supports per-presentation ad control through `StorytellerClipsAdConfiguration`.\n\nEmbedded bottom banners are **opt-in** across every embedding entry point \u2014 `rememberStorytellerEmbeddedClipsState`, `StorytellerEmbeddedClipsState`, and both `StorytellerClipsFragment.create(...)` overloads. Pass an `adConfiguration` \u2014 directly, or via `StorytellerClipCollectionConfiguration.adConfiguration` \u2014 to enable the banner. A `StorytellerClipCollectionConfiguration` with no `adConfiguration` (`null`) keeps the bottom banner disabled for embedding; full-screen `Storyteller.openCollection` enables it when the configuration is omitted.\n\nEmbedded Clips continue to suppress the standard zero-index opening ad-as-Clip. When a correctly configured `StorytellerImaModule` is registered, `preRollEnabled = true` permits one true IMA pre-roll before the initially opened Embedded Clip; set it to `false` for a user who should not receive that pre-roll. `betweenClipsAdProviderOrder` controls the ordered allowlist for later standard ad slots, while `bottomBannerEnabled` controls only bottom banners. Nullable `frequency` and `initialIndex` values independently inherit remote cadence; valid supplied values override later cadence for this Embedded presentation. See [Per-presentation Clips Ad Controls](Ads.md#per-presentation-clips-ad-controls).\n\n```kotlin\nval storytellerClipsFragment = StorytellerClipsFragment.create(\n configuration = Storyteller.StorytellerClipCollectionConfiguration(\n collectionId = \"yourCollectionId\",\n adConfiguration = Storyteller.StorytellerClipsAdConfiguration(\n bottomBannerEnabled = true,\n preRollEnabled = false,\n betweenClipsAdProviderOrder = listOf(\n Storyteller.StorytellerAdProvider.VAST,\n Storyteller.StorytellerAdProvider.GAM,\n ),\n frequency = 4,\n initialIndex = 1,\n ),\n ),\n)\n```\n\n## Reload Data\n\n`StorytellerClipsFragment` in order to reload data you can call `reloadData()` method. This method will make a request to the\nbackend to fetch the latest data.\n\nIf this method is called when there are category filters applied, then it will go back one level. If there are no category filters applied this method will reload data.\n\nLoading state can be observed by setting `listener` property and overriding it's `onDataLoadStart` and `onDataLoadComplete` methods.\nThese loading callbacks apply to the top-level Clips collection load. Following feed loads do not trigger Embedded Clips loading callbacks.\n\n```kotlin\n val storytellerClipsFragment = StorytellerClipsFragment.create(\n collection = \"yourCollectionId\",\n topLevelBackEnabled = true,\n context = mapOf(\"placementId\" to \"embedded_clips\", \"location\" to \"ClipsScreen\")\n )\n storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener {\n override fun onTopLevelBackPressed(): Boolean {\n // will be invoked when top level back button is pressed\n return true // true if fragment needs to be stopped / host should handle back\n }\n\n override fun onDataLoadStarted() {\n // will be invoked when data load starts\n }\n\n override fun onDataLoadComplete(success: Boolean, error: Error?) {\n // will be invoked when data load completes or fails\n }\n }\n storytellerClipsFragment.reloadData()\n```\n\n## Inset Management\n\nUse `topInset` and `bottomInset` only when the Embedded Clips container is deliberately laid behind\nan occluding system surface. For example, `topInset` can offset the title and top controls when the\ncontainer extends behind the status bar. If the host has already consumed that system inset by\npadding or sizing the container, leave the corresponding Storyteller inset at `0` so it is not\napplied twice.\n\nThe Clips Instructions screen uses these same insets for its content. The **Tap to Start**\nbutton stays visible without scrolling, while the heading and instruction rows scroll above\nit on compact layouts. This applies to both Compose Embedded Clips and `StorytellerClipsFragment`.\n\nThese properties are not the integration seam for sibling host UI such as a clickable app bottom\nnavigation bar. Size the Embedded Clips container so its bottom edge ends at the top of that\nnavigation instead; see [Host navigation and bottom banners](#host-navigation-and-bottom-banners).\n\n```kotlin\nViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, winInsets ->\n val inset =\n winInsets.getInsets(\n WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars()\n )\n\n storytellerClipsFragment.topInset = inset.top\n storytellerClipsFragment.bottomInset = inset.bottom\n\n WindowInsetsCompat.CONSUMED\n}\n```\n\nDo not also apply the same values as container padding. Choose one owner for each system inset.\n\n## Host navigation and bottom banners\n\nEmbedded Clips fill the bounds supplied by the host. When the screen also contains clickable bottom\nnavigation, make the Storyteller container end at the navigation's top edge. Storyteller owns layout\ninside those local bounds; the host owns the navigation component and system bars. From Android SDK\n11.7.0, effective-bottom Embedded Clips use a compact 8dp progress region matching the track's\nmaximum visual height; no larger scrub target is reserved for this arrangement. When a\nwidth-constrained video creates enough measured black space below the media for the banner and its\n16dp lower band, the progress track uses its top edge at the\napproved position 16dp before the banner. Without qualifying bottom letterbox space, the current banner-before-progress order\nis preserved and the bottom-edge track remains at the local container boundary. Caller navigation and\nsystem navigation remain outside the measured letterbox, so do not add their heights as SDK padding.\nClips without a banner, direct/modal Clips, and eligible Embedded `ABOVE_ACTION` layouts retain the\nexisting 80dp target. A height-constrained host retains the legacy layout rather than hiding an\neligible banner.\n\nDo not add the host navigation height to `StorytellerEmbeddedClipsState.bottomInset` or\n`StorytellerClipsFragment.bottomInset` after excluding it from the container. Doing both creates\ndouble padding.\n\n### Full-height Compose host\n\nUse the `Scaffold` content bounds for `StorytellerEmbeddedClips`. The bottom bar remains a sibling\noutside the SDK container:\n\n```kotlin\nval state = rememberStorytellerEmbeddedClipsState(\n collectionId = \"your-collection-id\",\n topLevelBack = false,\n adConfiguration = Storyteller.StorytellerClipsAdConfiguration(\n bottomBannerEnabled = true,\n ),\n)\n\nScaffold(\n bottomBar = { HostBottomNavigation() },\n) { contentPadding ->\n Box(\n modifier = Modifier\n .fillMaxSize()\n .padding(contentPadding)\n .consumeWindowInsets(contentPadding),\n ) {\n StorytellerEmbeddedClips(\n modifier = Modifier.fillMaxSize(),\n state = state,\n )\n }\n}\n```\n\n### Full-height Fragment host\n\nConstrain the fragment container above the host navigation. The Fragment fills only the resulting\nlocal bounds:\n\n```xml\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\">\n\n <androidx.fragment.app.FragmentContainerView\n android:id=\"@+id/storyteller_clips_container\"\n android:layout_width=\"0dp\"\n android:layout_height=\"0dp\"\n app:layout_constraintBottom_toTopOf=\"@id/host_bottom_navigation\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\"\n app:layout_constraintTop_toTopOf=\"parent\" />\n\n <com.google.android.material.bottomnavigation.BottomNavigationView\n android:id=\"@+id/host_bottom_navigation\"\n android:layout_width=\"0dp\"\n android:layout_height=\"wrap_content\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\" />\n<\/androidx.constraintlayout.widget.ConstraintLayout>\n```\n\nApply gesture-navigation or three-button system insets to the host navigation or its parent exactly\nonce. Keep `bottomInset = 0` when those system bounds are already outside the fragment container.\n\n## Compose Integration\n\n`StorytellerClipsFragment` can be used in Jetpack Compose using the `StorytellerEmbeddedClips` composable.\nYou can optionally pass `clipId` to start the embedded player from a specific Clip in the collection.\nThis is useful when the host app has a Storyteller Clip ID for a selected content item, such as a\nlive-blog post or match-centre event.\n\n```kotlin\nclass DemoComposeEmbeddedClipsActivity : FragmentActivity() {\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n val collection = intent.getStringExtra(\"collection\") ?: \"\"\n val widthPercentage = intent.getIntExtra(\"width\", 100) / 100F\n val heightPercentage = intent.getIntExtra(\"height\", 80) / 100F\n val topLevelBack = intent.getBooleanExtra(\"topLevelBack\", false)\n val initialCategory = intent.getStringExtra(\"category\") ?: \"\"\n enableEdgeToEdge()\n setContent {\n val state = rememberStorytellerEmbeddedClipsState(\n collectionId = collection,\n topLevelBack = topLevelBack,\n initialCategory = initialCategory,\n context = mapOf(\"placementId\" to \"embedded_clips_compose\", \"location\" to \"ClipsScreen\")\n )\n StorytellerEmbeddedClips(\n modifier = Modifier\n .fillMaxWidth(widthPercentage)\n .fillMaxHeight(heightPercentage),\n state = state,\n )\n }\n }\n\n companion object {\n fun start(context: Context, collection: String, category: String?, width: Int, height: Int, topLevelBack: Boolean) {\n // add params to intent\n Intent(context, DemoComposeEmbeddedClipsActivity::class.java).apply {\n putExtra(\"collection\", collection)\n putExtra(\"category\", intialCategory)\n putExtra(\"width\", width)\n putExtra(\"height\", height)\n putExtra(\"topLevelBack\", topLevelBack)\n context.startActivity(this)\n }\n }\n }\n}\n```\n\n### Starting from a specific Clip in Compose\n\nUse the `clipId` parameter when the host app should open Embedded Clips on a specific Clip inside\nthe collection.\n\n```kotlin\nval state = rememberStorytellerEmbeddedClipsState(\n collectionId = \"your-collection-id\",\n clipId = \"your-clip-id\",\n topLevelBack = true,\n context = mapOf(\n \"placementId\" to \"embedded_clips\",\n \"location\" to \"LiveBlog\"\n )\n)\n\nStorytellerEmbeddedClips(\n modifier = Modifier,\n state = state\n)\n```\n\nIf `clipId` is omitted, Embedded Clips starts from the first available Clip in the collection.\n\n### StorytellerEmbeddedClipsState\n\n`rememberStorytellerEmbeddedClipsState` is a composable function that creates a `StorytellerEmbeddedClipsState` object that holds the state of the `StorytellerEmbeddedClips` composable.\nIt accepts an optional `clipId`. When provided, Embedded Clips starts playback from that Clip if it\nis available in the collection.\n\n```kotlin\nval state = rememberStorytellerEmbeddedClipsState(\n collectionId = \"collection\",\n topLevelBack = topLevelBack,\n adConfiguration = Storyteller.StorytellerClipsAdConfiguration(\n bottomBannerEnabled = false,\n frequency = 4,\n initialIndex = 1,\n ),\n context = mapOf(\"placementId\" to \"embedded_clips_compose\", \"location\" to \"ClipsScreen\")\n)\nStorytellerEmbeddedClips(\n modifier = Modifier,\n state = state\n)\n```\n\n`StorytellerEmbeddedClipsState` contains `canGoBack` property that can be used to check if the fragment can go back from the current Category or is it at the top level.\n\n```kotlin\nval state = rememberStorytellerEmbeddedClipsState(\n collectionId = \"collection\",\n topLevelBack = topLevelBack,\n context = mapOf(\"placementId\" to \"embedded_clips_compose\", \"location\" to \"ClipsScreen\")\n)\n\nval canGoBack = state.canGoBack // true if user can navigate back\n```\n\n`StorytellerEmbeddedClipsState` contains `goBack()` which will move the content to previous Category if the user is not at the top level.\n\n```kotlin\nval state = rememberStorytellerEmbeddedClipsState(\n collectionId = collection,\n topLevelBack = topLevelBack,\n context = mapOf(\"placementId\" to \"embedded_clips_compose\", \"location\" to \"ClipsScreen\")\n)\n\nstate.goBack() // navigate to previous category programmatically\n```\n", "copy_markdown_include_header": false, "base_path": "android", "ai_dir": "ai", "missing_payload_behavior": "empty"}