PageSourceSearch

https://bambuser.com/docs/assets/js/13ca5ff0.61028c33.js

js bambuser.com collected 2026-10-01 08:36:08 UTC 12,318 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkbambuser_docs||=[]).push([[456],{30477(e,n,i){i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>a,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>l});const t=JSON.parse('{"id":"mobileSDK/android/vc-android-picture-in-picture","title":"Android SDK \u2014 Picture-in-Picture","description":"Configure the floating mini-player, enter and exit PiP programmatically, control PiP frame size, and observe presentation changes from the widget.","source":"@site/video-consultation/mobileSDK/android/picture-in-picture.mdx","sourceDirName":"mobileSDK/android","slug":"/mobileSDK/android/mobile-sdk/android/picture-in-picture","permalink":"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/picture-in-picture","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"id":"vc-android-picture-in-picture","title":"Android SDK \u2014 Picture-in-Picture","description":"Configure the floating mini-player, enter and exit PiP programmatically, control PiP frame size, and observe presentation changes from the widget.","sidebar_label":"Picture in Picture","slug":"mobile-sdk/android/picture-in-picture"},"sidebar":"someSidebars","previous":{"title":"Shopper Events Tracking","permalink":"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/purchase-tracking"},"next":{"title":"Co-browse","permalink":"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/co-browse"}}');var o=i(74848),r=i(28453);const s={id:"vc-android-picture-in-picture",title:"Android SDK \u2014 Picture-in-Picture",description:"Configure the floating mini-player, enter and exit PiP programmatically, control PiP frame size, and observe presentation changes from the widget.",sidebar_label:"Picture in Picture",slug:"mobile-sdk/android/picture-in-picture"},a="Picture in Picture",d={},l=[{value:"Two PiP modes",id:"two-pip-modes",level:2},{value:"Entering and exiting PiP",id:"entering-and-exiting-pip",level:2},{value:"Entering PiP on checkout",id:"entering-pip-on-checkout",level:2},{value:"Auto-expand on connect",id:"auto-expand-on-connect",level:2},{value:"Frame size and position",id:"frame-size-and-position",level:2},{value:"Observing presentation changes",id:"observing-presentation-changes",level:2},{value:"Presentation transitions",id:"presentation-transitions",level:2},{value:"Related \u2014 audio during PiP",id:"related--audio-during-pip",level:2},{value:"See also",id:"see-also",level:2}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"picture-in-picture",children:"Picture in Picture"})}),"\n",(0,o.jsxs)(n.p,{children:["The Calls-shopper SDK's Picture-in-Picture is a ",(0,o.jsx)(n.strong,{children:"draggable floating mini-player"})," rendered by the SDK itself \u2014 not Android's system-level ",(0,o.jsx)(n.code,{children:"PictureInPictureParams"}),". The mini-player is a smaller frame of the same WebView the full-screen widget uses, so the video stream keeps flowing and every widget control still works, just at a smaller footprint."]}),"\n",(0,o.jsx)(n.h2,{id:"two-pip-modes",children:"Two PiP modes"}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"BambuserCallController"})," exposes:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"isPiP: Boolean"})," \u2014 is the overlay currently in mini-player mode?"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"pipPresentation: PipPresentation"})," \u2014 ",(0,o.jsx)(n.code,{children:"Floating"})," or ",(0,o.jsx)(n.code,{children:"Minimized"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"PipPresentation.Floating"})," is the standard mini-player (default ",(0,o.jsx)(n.code,{children:"180\xd7260 dp"}),"). ",(0,o.jsx)(n.code,{children:"PipPresentation.Minimized"})," is a more compact chip variant (default ",(0,o.jsx)(n.code,{children:"180\xd760 dp"}),") used when the widget wants an even smaller footprint (e.g. during a queue wait)."]}),"\n",(0,o.jsx)(n.h2,{id:"entering-and-exiting-pip",children:"Entering and exiting PiP"}),"\n",(0,o.jsxs)(n.p,{children:["The shopper enters PiP by tapping the widget's own minimize button. That triggers a ",(0,o.jsx)(n.code,{children:"viddget:viewport_mode"})," message which the SDK's event router translates to ",(0,o.jsx)(n.code,{children:"controller.isPiP = true"}),". Widget-initiated entries stay widget-controlled \u2014 no host code needed."]}),"\n",(0,o.jsx)(n.p,{children:"From the host side you can drive it programmatically:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:"bambuserCall.enterPiP()                                       // \u2192 Floating\nbambuserCall.enterPiP(presentation = PipPresentation.Minimized)  // \u2192 Minimized chip\nbambuserCall.expand()                                          // \u2192 back to full-screen\n"})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"enterPiP(presentation = \u2026)"})," drives ",(0,o.jsx)(n.strong,{children:"both sides"}),":"]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["Tells the widget to switch its own UI to the mini video player (via the embed's ",(0,o.jsx)(n.code,{children:"floatAbove"})," method). Otherwise the widget would keep rendering whatever it was showing \u2014 chat, cart, product detail \u2014 inside the shrunken frame."]}),"\n",(0,o.jsx)(n.li,{children:"Collapses the SDK's WebView layout to the floating / minimized frame."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"entering-pip-on-checkout",children:"Entering PiP on checkout"}),"\n",(0,o.jsxs)(n.p,{children:["The most common host-side use of ",(0,o.jsx)(n.code,{children:"enterPiP"})," is on the widget's checkout event. When the agent adds items to the shopper's cart and the shopper taps \"Checkout\" inside the widget, the SDK emits a ",(0,o.jsx)(n.code,{children:"Checkout"})," event. Host apps typically respond by shrinking to a floating mini-player and switching the underlying UI to the app's own checkout screen, so the shopper can complete the purchase while the call continues in the corner:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:"override fun onEmit(controller: BambuserCallController, event: BambuserCallEvent) {\n    when (event) {\n        is BambuserCallEvent.Checkout -> {\n            // Shrink to a floating mini video player so the shopper can\n            // complete checkout in the app underneath.\n            controller.enterPiP(presentation = PipPresentation.Floating)\n            navigation.switchTo(Route.Cart)   // your app's own checkout route\n        }\n        // \u2026\n    }\n}\n"})}),"\n",(0,o.jsx)(n.p,{children:"Without this, the widget's checkout tap would leave the video full-screen and the shopper would have no way to reach the app's native cart / payment UI."}),"\n",(0,o.jsx)(n.h2,{id:"auto-expand-on-connect",children:"Auto-expand on connect"}),"\n",(0,o.jsxs)(n.p,{children:["By default, when the call transitions to ",(0,o.jsx)(n.code,{children:"Connected"})," while in PiP, the SDK auto-restores the widget to full-screen so the shopper sees the agent's face immediately. Configure via:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:'BambuserCallConfiguration(\n    orgId = "\u2026",\n    environment = BambuserEnvironment.US,\n    autoExpandOnConnect = true,   // default\n)\n'})}),"\n",(0,o.jsxs)(n.p,{children:["Set to ",(0,o.jsx)(n.code,{children:"false"})," if you want the shopper to stay in PiP through the connection and expand manually."]}),"\n",(0,o.jsx)(n.h2,{id:"frame-size-and-position",children:"Frame size and position"}),"\n",(0,o.jsxs)(n.p,{children:["The mini-player defaults to ",(0,o.jsx)(n.code,{children:"180\xd7260 dp"})," (",(0,o.jsx)(n.code,{children:"Floating"}),") and ",(0,o.jsx)(n.code,{children:"180\xd760 dp"})," (",(0,o.jsx)(n.code,{children:"Minimized"}),"). Both are configurable at init time:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:'import androidx.compose.ui.unit.DpSize\nimport androidx.compose.ui.unit.dp\n\nBambuserCallConfiguration(\n    orgId = "\u2026",\n    environment = BambuserEnvironment.US,\n    floatingPipSize = DpSize(200.dp, 300.dp),\
1n    // minimizedPipSize is internal \u2014 override via configuration if exposed\n)\n'})}),"\n",(0,o.jsx)(n.p,{children:"The mini-player is anchored bottom-right by default. The shopper can drag it anywhere on screen \u2014 the drag gesture only fires in PiP mode (never in full-screen, so it never competes with WebKit's own gesture recognizers)."}),"\n",(0,o.jsxs)(n.p,{children:["Positioning constants (margin from screen edges, bottom safe-area reserve) are internal fields on the configuration; the defaults (",(0,o.jsx)(n.code,{children:"pipMarginDp = 16"}),", ",(0,o.jsx)(n.code,{children:"bottomReserveDp = 0"}),") are appropriate for most apps."]}),"\n",(0,o.jsx)(n.h2,{id:"observing-presentation-changes",children:"Observing presentation changes"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:"BambuserCallDelegate"})," interface receives ",(0,o.jsx)(n.code,{children:"PresentationChanged"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:'override fun onEmit(controller: BambuserCallController, event: BambuserCallEvent) {\n    if (event is BambuserCallEvent.PresentationChanged) {\n        Log.d("BambuserCall",\n            "PiP=${event.isPiP}, presentation=${event.presentation}")\n    }\n}\n'})}),"\n",(0,o.jsxs)(n.p,{children:["Compose hosts can observe directly on the controller (the state fields are ",(0,o.jsx)(n.code,{children:"mutableStateOf"}),", so reading them in a ",(0,o.jsx)(n.code,{children:"@Composable"})," recomposes automatically):"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-kotlin",children:"@Composable\nfun MyView(call: BambuserCallController) {\n    val isPiP = call.isPiP\n    LaunchedEffect(isPiP) {\n        // react to PiP transitions\n    }\n}\n"})}),"\n",(0,o.jsx)(n.h2,{id:"presentation-transitions",children:"Presentation transitions"}),"\n",(0,o.jsxs)(n.p,{children:["The full-screen \u2194 PiP animation uses Compose's ",(0,o.jsx)(n.code,{children:"animateDpAsState"})," on the WebView's width/height, plus ",(0,o.jsx)(n.code,{children:"clip(RoundedCornerShape(16.dp))"})," and a shadow applied only in PiP for the mini-player look. Transition duration is ",(0,o.jsx)(n.code,{children:"220 ms"})," by default and configurable via ",(0,o.jsx)(n.code,{children:"BambuserCallConfiguration.transitionAnimationDurationMs"}),"."]}),"\n",(0,o.jsx)(n.h2,{id:"related--audio-during-pip",children:"Related \u2014 audio during PiP"}),"\n",(0,o.jsxs)(n.p,{children:["The SDK auto-grants camera and microphone permissions to the WebView, but Android still needs the manifest permissions and hardware acceleration. See ",(0,o.jsx)(n.a,{href:"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/installation",children:"Installation"})," for the full manifest snippet."]}),"\n",(0,o.jsx)(n.h2,{id:"see-also",children:"See also"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/co-browse",children:"Co-browse"})," \u2014 how PiP interacts with in-app navigation during a call."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.a,{href:"/docs/video-consultation/mobileSDK/android/mobile-sdk/android/architecture",children:"Architecture"})," \u2014 the presentation stack in detail."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>s,x:()=>a});var t=i(96540);const o={},r=t.createContext(o);function s(e){const n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);

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.