尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GSYVideoPlayer 与 Jetpack Compose 集成实战:AndroidView 桥接、Compose 原生播放器与生命周期管控

GSYVideoPlayer 与 Jetpack Compose 集成实战:AndroidView 桥接、Compose 原生播放器与生命周期管控 GSYVideoPlayer 与 Jetpack Compose 集成实战AndroidView 桥接、Compose 原生播放器与生命周期管控【免费下载链接】GSYVideoPlayerVideo players (IJKplayer, ExoPlayer, MediaPlayer), HTTPS, 16k page size, danmaku (bullet chat) support, external subtitles, support for filters, watermarks, and GIF screenshots, pre-roll and mid-roll ads, multiple simultaneous playback, basic seeking/dragging, volume and brightness adjustment, play-while-cache support项目地址: https://gitcode.com/GitHub_Trending/gs/GSYVideoPlayer导读本文聚焦 GSYVideoPlayer 官方 Compose 桥接层gsyVideoPlayer-compose系统讲解如何在 Jetpack Compose 工程中以WrapperAndroidView 包装与Native纯 Compose 控件两种模式内嵌播放器并深入源码剖析GSYPlayerController的状态流、事件流、生命周期自动桥接与手势迁移实现。读完本文你将掌握在列表、竖直短视频、广告混排等场景下将 GSYVideoPlayer 无缝接入 Compose 的最小可运行代码、参数语义与常见坑规避方案。一、模块定位gsyVideoPlayer-compose是什么gsyVideoPlayer-compose是仓库中独立的 Compose 桥接模块模块描述为 Jetpack Compose wrapper and native Composables for GSYVideoPlayer见 gsyVideoPlayer-compose/gradle.properties。它不改变 GSYVideoPlayer 的播放内核而是在内核之上提供两类接入方式模式入口说明Wrapper推荐上手GSYVideoPlayerView.kt / GSYAnyVideoPlayerView.ktAndroidView直接承载StandardGSYVideoPlayer/ 任意NormalGSYVideoPlayer子类保留全部内置能力全屏、手势、缓存、字幕、滤镜等Native纯 Compose 控件GSYPlayerSurface.kt 中的rememberGSYPlayerControllerGSYPlayerSurface配合 GSYDefaultControls.kt只承载画面控制层完全用 Compose 自绘UI 可完全定制通用工具LifecycleBridge.ktLifecycleEffect { event - ... }用于统一订阅宿主 Lifecycle 事件两种模式的取舍非常明确Wrapper 模式牺牲 UI 定制换取零成本继承——所有 Java 侧内置能力原样可用适合快速接入Native 模式用GSYPlayerController把状态/事件/控制命令响应式化控制 UI 完全由 Compose 自绘适合深度定制播放器界面的场景。两种模式共享同一套生命周期桥接策略。二、Wrapper 模式 API 详解2.1GSYVideoPlayerView标准播放器包装GSYVideoPlayerView是 Wrapper 模式的默认入口内部通过AndroidView创建一个StandardGSYVideoPlayer实例见 GSYVideoPlayerView.kt#L28-L36Composable fun GSYVideoPlayerView( modifier: Modifier Modifier, setUp: (StandardGSYVideoPlayer) - Unit, setUpKey: Any? null, autoReleaseOnDispose: Boolean true, autoPauseResume: Boolean true, onPlayerCreated: (StandardGSYVideoPlayer) - Unit {}, )参数语义如下参数类型默认值说明modifierModifierModifier布局修饰符通常Modifier.fillMaxWidth().aspectRatio(16f/9)setUp(StandardGSYVideoPlayer) - Unit必填配置 player 的 lambda等价于GSYVideoOptionBuilder.build(player)setUpKeyAny?null非空时其变化会重新调用setUp典型用法传 url为null时仅在 factory 阶段调用一次setUpautoReleaseOnDisposeBooleantrue离屏离开 composition时自动player.release()autoPauseResumeBooleantrue自动桥接 LifecycleON_PAUSE → GSYVideoManager.onPause()、ON_RESUME → onResume()onPlayerCreated(StandardGSYVideoPlayer) - Unit{}拿到 player 实例的回调可用于持有引用实现要点源码级setUpKey的去重逻辑非常关键。源码用lastAppliedKey单元素数组记录上一次已应用的 keyAndroidView的updateblock 中只有setUpKey ! null lastAppliedKey[0] ! setUpKey时才重新调用setUpGSYVideoPlayerView.kt#L56-L64。这避免了两类问题一是每次重组都触发builder.build导致播放重置二是update拿到过期闭包。同时源码用rememberUpdatedState(setUp)保证update里始终拿到最新 lambda避免闭包引用过期外部变量。2.2GSYAnyVideoPlayerViewT任意子类通用包装相比GSYVideoPlayerView通用版多出一个factory参数GSYAnyVideoPlayerView.kt#L26-L35Composable fun T : NormalGSYVideoPlayer GSYAnyVideoPlayerView( modifier: Modifier Modifier, factory: (Context) - T, setUp: (T) - Unit, setUpKey: Any? null, autoReleaseOnDispose: Boolean true, autoPauseResume: Boolean true, onPlayerCreated: (T) - Unit {}, )参数类型说明factory(Context) - T传自定义子类如ListGSYVideoPlayer、SampleControlVideo、DanmakuVideoPlayer等NormalGSYVideoPlayer子类其余参数与GSYVideoPlayerView完全一致。设计上该通用版不直接依赖 app 模块的子类仅依赖NormalGSYVideoPlayer基类gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video由调用方通过factory决定具体类型——这意味着即使你的自定义播放器类只存在于业务模块也能通过factory传入使用。三、Native 模式 API 详解3.1rememberGSYPlayerController响应式控制器Native 模式的核心是 GSYPlayerSurface.kt#L26-L32 中的rememberGSYPlayerControllerComposable fun rememberGSYPlayerController( url: String? null, cacheWithPlay: Boolean false, title: String , autoPlay: Boolean false, autoPauseResume: Boolean true, ): GSYPlayerController参数默认值说明urlnull可空非空时内部LaunchedEffect触发controller.setUp(url, cacheWithPlay, title, autoPlay)cacheWithPlayfalse边播边缓存title视频标题透传给底层 playerautoPlayfalseattach 完成后是否自动startPlayLogicautoPauseResumetrue自动订阅 LifecycleON_PAUSE → GSYVideoManager.onPause()、ON_RESUME → onResume()源码约束值得注意setUp没有放在remember的 calculation lambda 里而是放在LaunchedEffect(controller, url, cacheWithPlay, title, autoPlay)中GSYPlayerSurface.kt#L34-L44。注释明确解释了原因把副作用放在读 state的位置会在重组期被多次调用无法保证只在 key 变化时触发一次LaunchedEffect则保证 key 变化时取消上一次协程并重新执行、离开 composition 时自动清理、且在主线程协程作用域内执行避免竞态。这是 Compose 中副作用必须进 Effect的教科书式示范。autoPauseResume桥接时用了runCatching包裹GSYVideoManager.onPause()/onResume()GSYPlayerSurface.kt#L49-L60因为内核会判别当前是否处于播放态——pause 不会破坏 Idle/Completed 等状态但防御性捕获可避免异常上抛到重组链路。少数业务悬浮窗、PiP、自定义后台播放需要保持后台播放应显式传false。3.2GSYPlayerSurface与GSYComposePlayerGSYPlayerSurface只承载画面、不绘制任何控制 UIGSYPlayerSurface.kt#L67-L89。其factory中创建GSYComposeHostPlayer并调用controller.attachHost(player)onRelease中调用controller.detachHost()避免 controller 仍持有已被 detach 的 view 造成内存泄漏。若不想自己组装GSYDefaultControls.kt提供了顶层封装GSYComposePlayer(controller, modifier, showDefaultControls)内部是Box { GSYPlayerSurface (可选) GSYDefaultControls }。GSYDefaultControls是完全 Compose 实现的默认控制条中间播放/暂停/重播按钮、缓冲转圈、底部进度 Slider 时长文本并演示了拖拽期间仅更新本地预览、抬手才一次性controller.seekTo的细节GSYDefaultControls.kt#L140-L157。3.3GSYPlayerController控制面rememberGSYPlayerController返回的GSYPlayerControllerGSYPlayerController.kt提供完整控制命令.play()/.pause()/.resume()/.togglePlayPause()/.retry().seekTo(ms)/.seekRelative(deltaMs).setSpeed(speed, soundTouch true).setUp(url, cacheWithPlay, title, autoPlay)/.setUp(builder, autoPlay).enterFullscreen(activity, hideActionBar, hideStatusBar)/.exitFullscreen(activity)窗口层全屏复用 GSY 内核startWindowFullscreen全屏期间内核会克隆第二个 host内部 dispatcher 自动跟随事件不丢失.dispose()仅由rememberGSYPlayerController的DisposableEffect在 Composable 退出时调用调用后所有 setter / setUp 变 no-op此外还有一组直 setterP1-5 设计setHeaders(map)、setCachePath(file)、setSeekOnStart(ms)、setLooping(bool)、setStartAfterPrepared(bool)、setOverrideExtension(str)、setShowPauseCover(bool)、setReleaseWhenLossAudio(bool)。这些直 setter 采用主线程门 released no-op 字段缓存设计GSYPlayerController.kt#L580-L607字段值会缓存到 controller 上attach 新 host含全屏克隆体时通过reapplyPendingSetters自动重放避免丢配置。3.4 状态读取GSYPlayerSnapshotStateFlow播放状态由 GSYPlayerState.kt 定义双通道暴露snapshot: StateGSYPlayerSnapshotCompose 友好的读取入口val snap by controller.snapshot即可响应式消费stateFlow: StateFlowGSYPlayerSnapshot协程形态适合在 ViewModel / 业务层collect。GSYPlayState枚举完整映射内核七种状态Idle / Preparing / Playing / Buffering / Paused / Completed / Error来自GSYVideoView.CURRENT_STATE_*见 GSYPlayerController.kt#L431-L441。GSYPlayerSnapshot除state、currentPosition、duration、bufferPercent、isPlaying外还包含扩展字段videoWidth/videoHeight、videoSarNum/videoSarDen用于修正非方形像素源纵横比、netSpeed/netSpeedText实时网速、isCacheReady是否在读本地缓存、speed、isLocked。状态同步由主线程tickRunnable每 500ms 轮询syncFromHost完成snapshot与stateFlow双写守恒GSYPlayerController.kt#L470-L476。3.5 事件订阅GSYPlayerEventSharedFlow边沿事件走events: SharedFlowGSYPlayerEventGSYPlayerEvent.kt。设计取舍源码注释原文要点Snapshot描述当前状态适合StateFlow而onPrepared / onAutoComplete这类错过了就错过了的瞬时事件以及onError(what, extra)这类一次性带数据信号用SharedFlow表达更自然塞进 data class 会让消费方被迫做事件去重。事件全集覆盖VideoAllCallBack22 项语义事件中的全部边沿事件StartPrepared / Prepared / AutoComplete / Complete / Error(what, extra) / EnterFull / QuitFull / EnterSmall / QuitSmall / ClickStartIcon / ClickStartError / ClickStartThumb / ClickResume / ClickResumeFullscreen / ClickStop / ClickStopFullscreen / ClickSeekbar / ClickSeekbarFullscreen / ClickBlank / ClickBlankFullscreen / TouchScreenSeekVolume / TouchScreenSeekPosition / TouchScreenSeekLight并额外补上来自GSYMediaPlayerListener的BufferingProgress(percent)与SeekComplete两项底层事件。SharedFlow配置为replay 0、extraBufferCapacity 16、DROP_OLDEST订阅前发生的事件不会重放极端情况下只丢老的。回调注入约束GSYPlayerController内部维护一个稳定internalDispatcher实例挂在 host 的mVideoAllCallBack槽位用户回调通过setUserVideoAllCallBack(callback)注入实现链式分发——禁止直接在 host 上调用setVideoAllCallBack(yourCb)那会把 dispatcher 顶掉导致 events / setOnXxx 全部失效GSYPlayerController.kt#L288-L302。旧式setOnError / setOnComplete / setOnPrepared已标记Deprecated官方建议改用controller.events.collect { ... }。3.6 逃生口withHostwithHost(block)是 Native 模式的逃生口GSYPlayerController.kt#L328-L338以主线程 released 安全 null 安全方式直接访问底层StandardGSYVideoPlayer调用 Compose 端尚未封装的方法字幕、镜像、滤镜、截图、GIF 等controller.withHost { player - player.setSubTitle(https://example.com/sub.srt) } val bmp controller.withHost { player - player.bitmap }注意必须在主线程调用内部不做线程切换避免与 GSY 内核线程模型冲突controller 已release()后返回nullblock 内禁止调用player.setVideoAllCallBack(...)。四、最小可运行示例4.1 Wrapper 模式GSYVideoPlayerView( modifier Modifier.fillMaxWidth().aspectRatio(16f/9), setUpKey url, setUp { player - GSYVideoOptionBuilder() .setUrl(url) .setCacheWithPlay(true) .setVideoTitle(demo) .setIsTouchWiget(true) .setAutoFullWithSize(true) .build(player) player.startPlayLogic() } )GSYVideoOptionBuilder位于 gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoOptionBuilder.java是 Java 侧的标准配置入口build(player)会把全部链式配置写入 player。完整可运行的工程示例见 app/.../compose/host/BasicWrapperActivity.kt该 Demo 还演示了setSeekRatio / setShowPauseCover / setReleaseWhenLossAudio / setStartAfterPrepared等高频 builder 选项以及通过onPlayerCreated持有 player 引用后手动startPlayLogic / onVideoPause / onVideoResume的写法。4.2 Native 模式val controller rememberGSYPlayerController(url url, autoPlay true) Box(Modifier.fillMaxWidth().aspectRatio(16f/9)) { GSYPlayerSurface(controller controller, modifier Modifier.matchParentSize()) GSYDefaultControls(controller controller) }也可以一步到位使用GSYComposePlayer(controller controller, modifier ...)。更完整的 Native 演示见 app/.../compose/host/DetailNativeActivity.kt 与 FullFeatureNativeActivity.kt。4.3 手动控制示例事件 状态订阅val controller rememberGSYPlayerController(url url) // 边沿事件播放完成 → 自动重播 LaunchedEffect(controller) { controller.events.collect { event - when (event) { is GSYPlayerEvent.AutoComplete - controller.seekTo(0) is GSYPlayerEvent.Error - Log.e(GSY, err ${event.what}/${event.extra}) else - Unit } } } // 状态驱动播放中显示进度 val snap by controller.snapshot Text(${snap.currentPosition} / ${snap.duration})五、生命周期最佳实践5.1 开箱即用的自动桥接Wrapper 与 Native 两种模式默认autoPauseResume true都会通过 LifecycleBridge.kt 的LifecycleEffect订阅宿主 Lifecycle在ON_PAUSE时调用GSYVideoManager.onPause()、ON_RESUME时调用GSYVideoManager.onResume()。GSYVideoManager是 Java 侧全局门面gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/GSYVideoManager.java与 Java 端DetailPlayer等走同一通路。因此默认情况下HOME / 切后台 → 自动暂停回前台 → 自动恢复。LifecycleEffect与官方lifecycle-runtime-compose 2.8自带的LifecycleEventEffect的差异源码注释明确说明官方版一次只接收一个特定Lifecycle.Event同时处理 ON_PAUSE / ON_RESUME / ON_DESTROY 需写 3 次 EffectLifecycleEffect一次拿到所有event更贴合同一个 player 实例跨多个生命周期事件联动的写法。新代码若只关心单个事件推荐直接用官方LifecycleEventEffect。5.2 手动桥接模板当autoPauseResume false或需要更细粒度控制时如播放中弹出详情页、或需要 release可自行桥接val lifecycle LocalLifecycleOwner.current.lifecycle DisposableEffect(lifecycle) { val obs LifecycleEventObserver { _, e - when (e) { Lifecycle.Event.ON_PAUSE - state.pause() Lifecycle.Event.ON_RESUME - state.resumeIfNeeded() Lifecycle.Event.ON_DESTROY- state.release() else - Unit } } lifecycle.addObserver(obs) onDispose { lifecycle.removeObserver(obs) } }这里的state.resumeIfNeeded()语义上等同于仅当处于暂停态才恢复避免对 Idle/Completed 等状态误操作——GSYVideoManager.onPause()/onResume()内核本身会判别当前状态安全。5.3 悬浮窗 / 后台播放例外需要保持后台播放的业务悬浮窗、PiP、自定义后台播放必须显式传autoPauseResume false否则切后台会自动暂停。仓库中悬浮窗场景可对照 app/.../compose/host/FloatingWindowComposeActivity.kt。六、手势Compose 端复刻 GSY 三向手势Native 模式不继承GSYVideoControlView的 Java 手势为此仓库在 GSYGestureModifier.kt 提供了Modifier.gsyGestureControl(...)把原生那套竖向左半屏调亮度 / 竖向右半屏调音量 / 横向调进度完整迁移到 ComposeModifier .gsyGestureControl( controller controller, enableSeek true, enableVolume true, enableBrightness true, onGestureUpdate { update - // 渲染中央 toast / 进度浮层无状态UI 由调用方自绘 }, onGestureCommit { update - // 仅 progress 手势松手 seek 落定时触发 }, )源码要点GSYGestureModifier.kt#L62-L83垂直 drag 以起始 X 是否小于屏宽一半区分左右半屏enableVolume / enableBrightness可分别关掉右/左半屏水平 drag 仅在enableSeek true且duration 0时生效drag 中只回调onGestureUpdate松手才controller.seekTo并触发onGestureCommit锁屏短路controller.snapshot.value.isLocked true时所有手势短路返回连 down 都不消费保留 Compose 上层 click/tap 通过音量算法与原生GSYVideoControlView.touchSurfaceMove一致每像素映射3 * max / height步亮度算法与onBrightnessSlide一致均为迁移而非重写。与原生差异原 ControlView 把手势状态写在自身字段且 dialog 由内置 popup 弹出Compose 版要求无状态 可定制 UIdialog/浮层由调用方通过onGestureUpdate自行渲染。七、Demo 对照官方 24 个 Compose 样例样例 hosts 全部位于 app/src/main/java/com/example/gsyvideoplayer/compose/host入口统一在 ComposeDemoListActivity.kt。以下为 SKILL 文档给出的场景对照表均已转换为仓库相对路径场景Activity原生桥接示例DetailNativeActivity.kt / BasicWrapperActivity.kt / FullFeatureNativeActivity.kt广告 主片列表内AdInListComposeActivity.kt列表 全屏ListWithFullscreenActivity.kt / ListPlayNativeActivity.kt / AutoPlayListActivity.kt抖音式竖屏VerticalShortVideoComposeActivity.kt滤镜DetailFilterComposeActivity.kt无缝切源 / Exo 切源SwitchSeamlessComposeActivity.kt / ExoSwitchSourceComposeActivity.kt / SwitchUrlActivity.kt字幕SubtitleComposeActivity.kt弹幕DanmakuComposeActivity.kt缓存 / 下载CacheDownloadComposeActivity.kt硬解 / MediaCodecMediaCodecComposeActivity.kt悬浮小窗FloatingWindowComposeActivity.kt音频独立AudioOnlyComposeActivity.kt多类型混排MoreTypeComposeActivity.kt多窗口并行MultiWindowActivity.kt / MultiWindowParallelComposeActivity.kt本地文件LocalFileComposeActivity.kt自定义主题CustomControlsThemeComposeActivity.ktWebView 详情WebDetailComposeActivity.kt这些样例覆盖了列表自动播放、抖音式竖屏、广告混排、无缝切源、弹幕、字幕、缓存下载、MediaCodec 硬解、悬浮窗、多窗口并行等几乎全部 GSYVideoPlayer 能力在 Compose 下的落地方式是 Wrapper/Native 两种 API 最直接的实战参考。八、常见坑与规避SKILL 文档总结的四个高频坑结合源码逐一说明成因Compose 频繁重组 →AndroidView的factory只跑一次不要把setUp放在factory里应放到updateblock并用remember(url)做去重。更稳妥的做法是直接用setUpKey参数——源码已用lastAppliedKey实现key 变化才重 build见 2.1 节setUpKey null时整个 update 等价 no-op不破坏老用法。AndroidView内部布局要指定固定高度或aspectRatio否则 SurfaceView 会 0 高度导致黑屏。所有官方 Demo 均使用Modifier.fillMaxWidth().aspectRatio(16f/9)或fillMaxSize()包裹。竖直短视频 Pager切页切页时用pagerState.currentPage触发state.play(url) / previousState.pause()释放放在DisposableEffect(pagerState)里。对照实现见 AutoPlayListActivity.kt 与 VerticalShortVideoComposeActivity.kt。与 Compose 主题深浅色切换GSYVideoPlayerCompose是原生 View 层Wrapper 模式承载的是StandardGSYVideoPlayerView不随 Compose 主题自动变色配色需要单独调用setBottomProgressBarDrawable等 Java APINative 模式的GSYDefaultControls则完全跟随 Compose 主题。自定义主题对照 CustomControlsThemeComposeActivity.kt。九、依赖接入gsyVideoPlayer-compose以独立 Android Library 形式存在模块目录 gsyVideoPlayer-compose含 gradle.properties 与 consumer-rules.pro。它依赖 Java 侧核心模块gsyVideoPlayer-javaGSYVideoOptionBuilder、StandardGSYVideoPlayer、GSYVideoManager、VideoAllCallBack等均来自该模块因此接入时需同时引入gsyVideoPlayer-java与gsyVideoPlayer-compose两个模块。若需启用 R8/ProGuard 混淆仓库的消费规则与混淆配置说明可参考 skills/13-gsy-proguard-r8/SKILL.md 及 app/proguard-rules.pro。关于 ExoPlayer 内核、缓存、字幕、滤镜等能力在 Compose 中的进一步组合用法可继续查阅 doc/COMPOSE_USE.md 与仓库技能文档 skills/README.md 下的 11 号技能索引。总结gsyVideoPlayer-compose提供了清晰的两条路线Wrapper 路线GSYVideoPlayerView/GSYAnyVideoPlayerView用AndroidView直接复用 Java 播放器保留全部内置能力适合快速接入与列表/广告等成熟场景Native 路线rememberGSYPlayerControllerGSYPlayerSurfaceGSYDefaultControls把状态、事件、控制命令全部响应式化控制 UI 完全 Compose 自绘配合gsyGestureControl手势迁移与withHost逃生口适合深度定制播放器界面。两条路线共享同一套LifecycleEffect生命周期桥接与主线程门 released 安全的约束体系而 24 个官方 Compose 样例则覆盖了从列表自动播放到多窗口并行的绝大多数生产场景可直接对照落地。【免费下载链接】GSYVideoPlayerVideo players (IJKplayer, ExoPlayer, MediaPlayer), HTTPS, 16k page size, danmaku (bullet chat) support, external subtitles, support for filters, watermarks, and GIF screenshots, pre-roll and mid-roll ads, multiple simultaneous playback, basic seeking/dragging, volume and brightness adjustment, play-while-cache support项目地址: https://gitcode.com/GitHub_Trending/gs/GSYVideoPlayer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表