PageSourceSearch

https://www.vdocipher.com/docs/assets/js/59049517.fff070e2.js

js vdocipher.com collected 2026-09-24 17:18:06 UTC 18,548 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkdocusaurus=self.webpackChunkdocusaurus||[]).push([[7827],{11470:(e,n,t)=>{t.d(n,{A:()=>I});var i=t(96540),a=t(34164),r=t(23104),o=t(56347),u=t(205),s=t(57485),l=t(31682),c=t(89466);function d(e){return i.Children.toArray(e).filter(e=>"\n"!==e).map(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad <Tabs> child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`)})?.filter(Boolean)??[]}function p(e){const{values:n,children:t}=e;return(0,i.useMemo)(()=>{const e=n??function(e){return d(e).map(({props:{value:e,label:n,attributes:t,default:i}})=>({value:e,label:n,attributes:t,default:i}))}(t);return function(e){const n=(0,l.X)(e,(e,n)=>e.value===n.value);if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map(e=>e.value).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e},[n,t])}function h({value:e,tabValues:n}){return n.some(n=>n.value===e)}function g({queryString:e=!1,groupId:n}){const t=(0,o.W6)(),a=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,s.aZ)(a),(0,i.useCallback)(e=>{if(!a)return;const n=new URLSearchParams(t.location.search);n.set(a,e),t.replace({...t.location,search:n.toString()})},[a,t])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=p(e),[o,s]=(0,i.useState)(()=>function({defaultValue:e,tabValues:n}){if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!h({value:e,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const t=n.find(e=>e.default)??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:r})),[l,d]=g({queryString:t,groupId:a}),[m,v]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[t,a]=(0,c.Dv)(n);return[t,(0,i.useCallback)(e=>{n&&a.set(e)},[n,a])]}({groupId:a}),y=(()=>{const e=l??m;return h({value:e,tabValues:r})?e:null})();(0,u.A)(()=>{y&&s(y)},[y]);return{selectedValue:o,selectValue:(0,i.useCallback)(e=>{if(!h({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);s(e),d(e),v(e)},[d,v,r]),tabValues:r}}var v=t(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var f=t(74848);function b({className:e,block:n,selectedValue:t,selectValue:i,tabValues:o}){const u=[],{blockElementScrollPositionUntilNextRender:s}=(0,r.a_)(),l=e=>{const n=e.currentTarget,a=u.indexOf(n),r=o[a].value;r!==t&&(s(n),i(r))},c=e=>{let n=null;switch(e.key){case"Enter":l(e);break;case"ArrowRight":{const t=u.indexOf(e.currentTarget)+1;n=u[t]??u[0];break}case"ArrowLeft":{const t=u.indexOf(e.currentTarget)-1;n=u[t]??u[u.length-1];break}}n?.focus()};return(0,f.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.A)("tabs",{"tabs--block":n},e),children:o.map(({value:e,label:n,attributes:i})=>(0,f.jsx)("li",{role:"tab",tabIndex:t===e?0:-1,"aria-selected":t===e,ref:e=>u.push(e),onKeyDown:c,onClick:l,...i,className:(0,a.A)("tabs__item",y.tabItem,i?.className,{"tabs__item--active":t===e}),children:n??e},e))})}function P({lazy:e,children:n,selectedValue:t}){const a=(Array.isArray(n)?n:[n]).filter(Boolean);if(e){const e=a.find(e=>e.props.value===t);return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return(0,f.jsx)("div",{className:"margin-top--md",children:a.map((e,n)=>(0,i.cloneElement)(e,{key:n,hidden:e.props.value!==t}))})}function w(e){const n=m(e);return(0,f.jsxs)("div",{className:(0,a.A)("tabs-container",y.tabList),
1children:[(0,f.jsx)(b,{...n,...e}),(0,f.jsx)(P,{...n,...e})]})}function I(e){const n=(0,v.A)();return(0,f.jsx)(w,{...e,children:d(e.children)},String(n))}},19365:(e,n,t)=>{t.d(n,{A:()=>o});t(96540);var i=t(34164);const a={tabItem:"tabItem_Ymn6"};var r=t(74848);function o({children:e,hidden:n,className:t}){return(0,r.jsx)("div",{role:"tabpanel",className:(0,i.A)(a.tabItem,t),hidden:n,children:e})}},28453:(e,n,t)=>{t.d(n,{R:()=>o,x:()=>u});var i=t(96540);const a={},r=i.createContext(a);function o(e){const n=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function u(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:o(e.components),i.createElement(r.Provider,{value:n},e.children)}},34874:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>u,metadata:()=>l,toc:()=>d});var i=t(74848),a=t(28453),r=t(11470),o=t(19365);const u={id:"pip-mode",title:"Implementing Picture in Picture Mode",sidebar_label:"Picture in Picture",hide_table_of_contents:!0},s=void 0,l={id:"mobile/android/pip-mode",title:"Implementing Picture in Picture Mode",description:"Overview",source:"@site/docs/mobile/android/pip-mode.md",sourceDirName:"mobile/android",slug:"/mobile/android/pip-mode",permalink:"/docs/mobile/android/pip-mode",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"pip-mode",title:"Implementing Picture in Picture Mode",sidebar_label:"Picture in Picture",hide_table_of_contents:!0},sidebar:"mobile",previous:{title:"Background Playback",permalink:"/docs/mobile/android/background-playback"},next:{title:"Jetpack Compose",permalink:"/docs/mobile/android/jetpack-compose"}},c={},d=[{value:"Overview",id:"overview",level:4},{value:"How users can interact with the PiP window",id:"how-users-can-interact-with-the-pip-window",level:4},{value:"Declare picture-in-picture support",id:"declare-picture-in-picture-support",level:4},{value:"Switch your activity to picture-in-picture",id:"switch-your-activity-to-picture-in-picture",level:4},{value:"Handle UI during picture-in-picture",id:"handle-ui-during-picture-in-picture",level:4},{value:"Handle audio interference with another app (Required only for player with custom controls)",id:"handle-audio-interference-with-another-app-required-only-for-player-with-custom-controls",level:4}];function p(e){const n={a:"a",code:"code",h4:"h4",li:"li",p:"p",pre:"pre",ul:"ul",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h4,{id:"overview",children:"Overview"}),"\n",(0,i.jsx)(n.p,{children:"Android supports PIP mode starting from android 8.0 (API level 26). Picture in Picture (PiP) is a special case of the multi-window UI which lets the user watch a video in a small window pinned to a corner of the screen while navigating between apps or browsing content on the main screen. To add PiP to your app, you need to register your activities that support PiP, switch your activity to PiP mode as needed, and make sure UI elements are hidden and video playback continues when the activity is in PiP mode. The PiP window appears in the topmost layer of the screen, in a corner chosen by the system."}),"\n",(0,i.jsx)(n.h4,{id:"how-users-can-interact-with-the-pip-window",children:"How users can interact with the PiP window"}),"\n",(0,i.jsx)(n.p,{children:"It is possible to drag the PiP window to another location. Starting in Android 12, users can also:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Single-tap the window to display a full-screen toggle, a close button, a settings button, and custom actions provided by your app (for example, play controls)."}),"\n",(0,i.jsx)(n.li,{children:"Double-tap the window to toggle between the current PiP size and maximum PiP size."}),"\n",(0,i.jsx)(n.li,{children:"Resize the PiP window using pinch-to-zoom."}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Your app controls when the current activity enters PiP mode. Here are some examples:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"An activity can enter PiP mode when the user taps the home button."}),"\n",(0,i.jsx)(n.li,{children:"Your app can move a video into PiP mode when the user navigates back from the video to browse other content."}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"declare-picture-in-picture-support",children:"Declare picture-in-picture support"}),"\n",(0,i.jsxs)(n.p,{children:["Register your video activity in your manifest by setting ",(0,i.jsx)(n.code,{children:"android:supportsPictureInPicture"})," to ",(0,i.jsx)(n.code,{children:"true"}),". Also, specify that your activity handles layout configuration changes as by setting ",(0,i.jsx)(n.code,{children:"android:configChanges"})," as mentioned below so that your activity doesn't relaunch when layout changes occur during PiP mode transitions."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-xml",children:'<activity\n  android:name=".PlayerActivity"\n  android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"\n  android:supportsPictureInPicture="true">\n'})}),"\n",(0,i.jsx)(n.h4,{id:"switch-your-activity-to-picture-in-picture",children:"Switch your activity to picture-in-picture"}),"\n",(0,i.jsxs)(n.p,{children:["To enter picture-in-picture mode, an activity must call ",(0,i.jsx)(n.a,{href:"https://developer.android.com/reference/android/app/Activity#enterPictureInPictureMode()",children:"enterPictureInPictureMode()"}),". For example, the following code switches an activity to PiP mode when a the user clicks the home or recent button:"]}),"\n",(0,i.jsxs)(r.A,{groupId:"java-kotlin-group",children:[(0,i.jsx)(o.A,{value:"java",label:"Java",default:!0,children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@Override\nprotected void onUserLeaveHint() {\n  super.onUserLeaveHint();\n  // switch to PiP mode if the user presses the home or recent button,\n  if (Build.VERSION.S
1DK_INT >= Build.VERSION_CODES.O) {\n    enterPictureInPictureMode(new PictureInPictureParams.Builder().build());\n  }\n}\n"})})}),(0,i.jsx)(o.A,{value:"kotlin",label:"Kotlin",children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-kotlin",children:"override fun onUserLeaveHint() {\n  super.onUserLeaveHint()\n  // switch to PiP mode if the user presses the home or recent button,\n  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n    enterPictureInPictureMode(PictureInPictureParams.Builder().build())\n  }\n}\n"})})})]}),"\n",(0,i.jsx)(n.h4,{id:"handle-ui-during-picture-in-picture",children:"Handle UI during picture-in-picture"}),"\n",(0,i.jsxs)(n.p,{children:["When the activity enters or exits picture-in-picture mode, the system calls ",(0,i.jsx)(n.a,{href:"https://developer.android.com/reference/android/app/Activity#onPictureInPictureModeChanged(boolean,%20android.content.res.Configuration)",children:"Activity.onPictureInPictureModeChanged()"})," or ",(0,i.jsx)(n.a,{href:"https://developer.android.com/reference/android/app/Fragment#onPictureInPictureModeChanged(boolean,%20android.content.res.Configuration)",children:"Fragment.onPictureInPictureModeChanged()"}),". You should override these callbacks to redraw the activity's UI elements. In PiP mode your activity is shown in a small window. PiP mode does not allow users to interact with your app's UI elements, and small details may be difficult to see. If your app needs to provide custom actions for PiP, see ",(0,i.jsx)(n.a,{href:"https://developer.android.com/guide/topics/ui/picture-in-picture#add_controls",children:"Add controls"})," in this document. Remove other UI elements before entering PiP, then restore them when you return to fullscreen:"]}),"\n",(0,i.jsxs)(r.A,{groupId:"java-kotlin-group",children:[(0,i.jsx)(o.A,{value:"java",label:"Java",default:!0,children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@Override\npublic void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {\n  super.onPictureInPictureModeChanged(isInPictureInPictureMode);\n  // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode for player with custom controls.\n  showControls(!isInPictureInPictureMode);\n\n  // Hide any non-player UI elements when entering PiP mode for a cleaner experience.\n  toggleOtherElementsVisibility(!isInPictureInPictureMode);\n}\n\nprivate void showControls(boolean show) {\n  if (show) {\n    playerControlView.show();\n  } else {\n    playerControlView.hide();\n  }\n}\n"})})}),(0,i.jsx)(o.A,{value:"kotlin",label:"Kotlin",children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-kotlin",children:"override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {\n  super.onPictureInPictureModeChanged(isInPictureInPictureMode)\n  // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode for player with custom controls.\n  showControls(!isInPictureInPictureMode)\n\n  // Hide any non-player UI elements when entering PiP mode for a cleaner experience.\n  toggleOtherElementsVisibility(!isInPictureInPictureMode)\n}\n\nprivate fun showControls(show: Boolean) {\n  if (show) {\n    playerControlView.show()\n  } else {\n    playerControlView.hide()\n  }\n}\n"})})})]}),"\n",(0,i.jsx)(n.h4,{id:"handle-audio-interference-with-another-app-required-only-for-player-with-custom-controls",children:"Handle audio interference with another app (Required only for player with custom controls)"}),"\n",(0,i.jsxs)(n.p,{children:["Video playback in the PiP window can cause audio interference with another app. To avoid this, request audio focus when you start playing the video and handle audio focus change notifications, as described in ",(0,i.jsx)(n.a,{href:"https://developer.android.com/guide/topics/media-apps/audio-focus",children:"Managing Audio Focus"}),". In PiP mode, pause or stop video playback if you receive a notification of audio focus loss."]}),"\n",(0,i.jsxs)(r.A,{groupId:"java-kotlin-group",children:[(0,i.jsx)(o.A,{value:"java",label:"Java",default:!0,children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:'private AudioManager audioManager;\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n  super.onCreate(savedInstanceState);\n  audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);\n}\n\nprivate boolean requestAudioFocus(AudioManager audioManager) {\n  return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);\n}\n\n@Override\npublic void onPlayerStateChanged(boolean playWhenReady, int playbackState) {\n  boolean playing = playbackState != VdoPlayer.STATE_IDLE && playbackState != VdoPlayer.STATE_ENDED && playWhenReady;\n\n  if (playing) {\n    // Request audio focus and notify other players to stop playback.\n    if (!requestAudioFocus(audioManager)) {\n      Log.i(TAG, "Audio focus not granted");\n    }\n  } else if (playbackState == VdoPlayer.STATE_ENDED) {\n    // Abandon audio focus when playback complete\n    audioManager.abandonAudioFocus(audioFocusChangeListener);\n  }\n}\n\nprivate final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {\n  @Override\n  public void onAudioFocusChange(int focusChange) {\n    switch (focusChange) {\n      case AudioManager.AUDIOFOCUS_GAIN:\n        // Resume playing\n        if (player != null) player.setPlayWhenReady(true);\n        break;\n      case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:\n        // Depending on your app, reduce audio to a minimum if you want to continue playing or pause\n        break;\n      case AudioManager.AUDIOFOCUS_LOSS:\n        // Pause playing\n        if (player != null) player.setPlayWhenReady(false);\n        break;\n      case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:\n      default:\n        // Stop playing\n        break;\n    }\n  }\n};\n'})})}),(0,i.jsx)(o.A,{value:"kotlin",label:"Kotlin",children:(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-kotlin",children:'private var audioManager: AudioManager? = null\n\noverride fun onCreate(savedInstanceState: Bundle?) {\n  super.onCreate(savedInstanceState)\n  audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager\n}\n\nprivate fun requestAudioFocus(audioManager: AudioManager): Boolean {\n  return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)\n}\n\noverride fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {\n  val playing = playbackState != VdoPlayer.STATE_IDLE && playbackState != VdoPlayer.STATE_ENDED && playWhenReady\n\n  if (playing) {\n    // Request audio focus and notify other players to stop playback.\n    if (!requestAudioFocus(audioManager!!)) {\n      Log.i(TAG, "Audio focus not granted")\n    }\n  } else if (playbackState == VdoPlayer.STATE_ENDED) {\n    // Abandon audio focus when playback complete\n    audioManager?.abandonAudioFocus(audioFocusChangeListener)\n  }\n}\n\nprivate val audioFocusChangeListener = object : AudioManager.OnAudioFocusChangeListener {\n  override fun onAudioFocusChange(focusChange: Int) {\n    when (focusChange) {\n      AudioManager.AUDIOFOCUS_GAIN ->
1 {\n        // Resume playing\n        player?.setPlayWhenReady(true)\n      }\n      AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {\n        // Depending on your app, reduce audio to a minimum if you want to continue playing or pause\n      }\n      AudioManager.AUDIOFOCUS_LOSS -> {\n        // Pause playing\n        player?.setPlayWhenReady(false)\n      }\n      AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,\n      else -> {\n        // Stop playing\n      }\n    }\n  }\n}\n'})})})]}),"\n",(0,i.jsxs)(n.p,{children:["For more details and best practices visit ",(0,i.jsx)(n.a,{href:"https://developer.android.com/guide/topics/ui/picture-in-picture",children:"Picture-in-picture (PiP) support"}),"."]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(p,{...e})}):p(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.