GSYVideoPlayer 近期播放能力源码级指南:从 WebVTT 进度预览到 DLNA 投屏

发布时间:2026/9/13 4:52:31
GSYVideoPlayer 近期播放能力源码级指南:从 WebVTT 进度预览到 DLNA 投屏 GSYVideoPlayer 近期播放能力源码级指南从 WebVTT 进度预览到 DLNA 投屏【免费下载链接】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本文基于仓库内的 RECENT_FEATURES_EN.md与中文版 RECENT_FEATURES.md 互为镜像整理而成系统梳理 GSYVideoPlayer 近期在进度条预览、外挂字幕、截图、GL 渲染、清晰度切换、播放器初始化稳定性、Exo 缓存生命周期以及 DLNA/UPnP 投屏上的能力变化。文中每一个能力都会给出 Demo 入口、核心类/API 以及对应源码位置并附带一份可直接执行的回归清单方便维护者在二次开发、升级或回归时快速定位入口与验证范围。能力入口总览下表汇总了本文涉及的全部近期能力、对应的 Demo 入口、主要实现类与一句话说明。它是后续各章节的导航图能力Demo 入口主要类说明WebVTT 进度条预览打开VIDEOPreViewGSYVideoPlayer通过 WebVTT 缩略图轨道展示预览图支持独立图片和 sprite 坐标裁剪。通用外挂字幕自定义EXO支持字幕、通用字幕非EXOGSYSubtitleController、GSYSubtitleViewSRT/WebVTT 由播放器 UI 层统一解析和渲染可跨 IJK、System、Media3 使用。完成后保留最后一帧完成保留最后一帧KeepLastFrameVideoDemo 级实现通过 flag 控制自然播放完成时保留当前渲染画面或回到默认封面态。截图语义增强滤镜 Demo、MediaCodec Demo 等StandardGSYVideoPlayer、GSYRenderView保留视频帧截图同时新增包含播放器 UI 的组合截图 API。GLSurfaceView 效果和生命周期滤镜DetailFilterActivity、GSYVideoGLView*Render整理 GL render 生命周期补充滤镜、纹理、多窗口、遮罩等 Demo 场景。多 URL 清晰度切换稳态优化无缝切换SmartPickVideo保留双 manager 方案强化切换过程中的位置同步、超时、失败回退和临时 manager 释放。Exo 自适应清晰度EXO自适应清晰度ExoAdaptiveTrackActivity、GSYExo2MediaPlayer使用单个 HLS master playlist 或 DASH MPD由 Media3 TrackSelector 在同一时间线内自适应或固定 video track。播放器初始化失败安全处理通用能力GSYVideoBaseManager、各IPlayerManager内核创建或初始化失败时走错误回调和资源清理避免直接 crash。Exo 缓存生命周期和 GIF 清理通用能力ExoSourceManager、GifCreateHelper收紧 Exo cache 的打开/释放流程GIF 生成流程结束或失败时更可靠地清理。DLNA/UPnP 投屏投屏 DemoCastCapability、JupnpDlnaProvider、JupnpDlnaSession、SampleCastControlVideo、CastDemoActivity协议无关 SPI 留在核心jUPnP 3.0.3 DLNAAVTransport:1实现由可选gsyvideoplayer-cast提供SetAVTransportURI → Play → Seek保留中途投屏本地进度并带单机 Loopback Receiver。这些能力在仓库文档体系中的覆盖范围如下README_CN.md/README.md首页能力摘要和近期能力入口。doc/USE.md / doc/USE_EN.md使用层面的 Demo 入口和核心 API。doc/UPDATE_VERSION.md / doc/UPDATE_VERSION_EN.mdUnreleased 版本变更摘要。doc/ARCHITECTURE.md播放能力在 UI、Manager、Render、Exo manager 等层级上的设计归属。doc/GSYVIDEO_PLAYER_PROJECT_INFO.md / doc/GSYVIDEO_PLAYER_PROJECT_INFO_EN.md项目结构说明里的近期能力层级映射。doc/SUBTITLE_CN.md通用字幕专题说明。doc/KEEP_LAST_FRAME.md / doc/KEEP_LAST_FRAME_EN.md完成后保留最后一帧专题说明。doc/RECENT_FEATURES.md / doc/RECENT_FEATURES_EN.md近期能力总览、API 和回归清单。构建、依赖、SO、发布、解码器、FAQ 类文档没有强行加入这些播放能力说明因为它们的主题不是 Demo 功能入口或播放架构。WebVTT 进度条预览从客户端抽帧到 WebVTT 缩略图轨道过去的进度条预览方案依赖客户端对原视频批量抽帧内存与耗时都不理想。现在的方案是业务侧预生成 WebVTT 缩略图轨道播放器按拖动进度查表取图。每个 cue时间区间可以指向一张独立图片也可以指向 sprite 雪碧图中的一块区域#xywh坐标裁剪WEBVTT 00:00:00.000 -- 00:00:05.000 thumbs.jpg#xywh0,0,160,90对应的演示播放器是PreViewGSYVideoPlayerDemo 入口打开VIDEO核心 API 只有两个player.setOpenPreView(true); // 开启进度条预览默认关闭 player.setPreviewVttUrl(https://example.com/thumbs.vtt); // 设置 WebVTT 轨道地址库层解析器与数据模型库层提供了三个类支撑该能力位于gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/preview/GSYVideoPreviewVttParserWebVTT 解析器负责把 VTT 文本解析为一组预览帧GSYVideoPreviewProvider预览帧提供者接口按时间点返回对应帧GSYVideoPreviewFrame单帧元数据包含startTimeMs/endTimeMs、imageUrl以及可选的cropX/cropY/cropWidth/cropHeight裁剪区域hasCrop()用于判断是否为 sprite 裁剪帧。从 GSYVideoPreviewVttParser.java 的实现可以看到几个关键细节按行解析--时间分隔符兼容hh:mm:ss.mmm与mm:ss.mmm两种时间格式见parseTimeMs遇到NOTE注释行自动跳过识别#xywh前缀并拆出x,y,w,h四个整数解析失败时回退为整图模式crop 为 -1图片地址会基于 VTT 的 baseUrl 做相对路径解析resolveUrl因此 VTT 内可以使用相对路径。GSYVideoPreviewFrame是一个不可变数据类构造函数同时提供整图模式GSYVideoPreviewFrame.java与 sprite 裁剪模式两个入口。仓库还提供了针对该解析器的单元测试 GSYVideoPreviewVttParserTest.java覆盖两个典型场景sprite 场景同一张160p-00001.jpg通过#xywh0,0,284,160与#xywh284,0,284,160切分两个时间片断言getPreviewFrame(1500)命中第二个帧且裁剪坐标正确cropX284、cropWidth284独立图片场景mm:ss.mmm短时间格式 相对图片路径断言解析后startTimeMs1000、endTimeMs2500、图片 URL 被正确拼为绝对地址。Demo 端如何取帧与渲染在PreViewGSYVideoPlayerapp/src/main/java/com/example/gsyvideoplayer/video/PreViewGSYVideoPlayer.java中预览流程为onStartTrackingTouch用户按住进度条时显示预览容器onProgressChanged中把progress换算成time progress * getDuration() / 100调用showPreView(time)取帧并把预览图随手指位置移动leftMargin 跟随进度showPreView通过mPreviewProvider.getPreviewFrame(time)拿到GSYVideoPreviewFramesprite 帧用PreviewSpriteTransformation按裁剪坐标变换加载独立帧直接 Glide 加载并centerCrop到 150x100 dp 的预览尺寸onStopTrackingTouch隐藏预览并恢复进度。VTT 的拉取在单线程ExecutorService中完成15 秒连接/读取超时通过自增mPreviewLoadId做请求时效校验——只有最新的加载结果才会生效过期回调直接丢弃避免快速切换 URL 时旧结果覆盖新结果。播放器进入全屏时startWindowFullscreen会同步透传mOpenPreView、mPreviewVttUrl与已加载的mPreviewProvider全屏与竖屏共用同一份预览数据。通用外挂字幕外挂字幕能力已从 Media3 专属实现调整为UI overlay 能力SRT/WebVTT 由GSYSubtitleController负责加载、解析并按播放进度刷新渲染在GSYSubtitleView上。由于字幕渲染在 UI 层完成它天然可以跨 IJK、System、Media3 等所有内核使用不再依赖某个播放内核的媒体源合并能力。字幕加载异常时只会清空字幕视图不会中断主视频播放。基本用法GSYSubtitleSource source new GSYSubtitleSource.Builder(https://example.com/subtitle.srt) .setMimeType(GSYSubtitleMime.APPLICATION_SUBRIP) .setLanguage(zh) .setLabel(Chinese) .setDefault(true) .build(); videoPlayer.setSubtitleSource(source); videoPlayer.setSubtitleEnabled(true);播放器基类 GSYVideoControlView.java 中与字幕相关的公开 API 还包括setSubtitleSources(ListGSYSubtitleSource)一次设置多个字幕源selectSubtitle(String id)按 id 切换当前字幕setSubtitleEnabled(boolean)/isSubtitleEnabled()开关字幕显示setSubtitleOffsetMs(long)/getSubtitleOffsetMs()字幕时间轴偏移用于音画不同步校正。GSYSubtitleControllerGSYSubtitleController.java内部通过update(long positionMs)随播放位置推进刷新当前 cue并支持字幕样式GSYSubtitleStyle与偏移量配置。详细的字幕接入说明见 doc/SUBTITLE_CN.md仓库同时在 subtitle 测试目录 提供了解析器级单元测试。完成后保留最后一帧KeepLastFrameVideoapp/src/main/java/com/example/gsyvideoplayer/video/KeepLastFrameVideo.java是 Demo 级实现核心思路是覆写自然播放完成后的 UI 状态切换在不主动释放 render view 的情况下保留当前渲染画面而不是立刻回到封面。开关方法为keepLastFrameVideo.setKeepLastFrameWhenComplete(true);从源码看onAutoCompletion覆写开启该 flag 后播放完成时记录mTextureViewContainer是否仍持有渲染视图用于后续判断是否保留画面走setStateAndUi(CURRENT_STATE_AUTO_COMPLETE)完成 UI 状态迁移释放音频焦点abandonAudioFocus、清除FLAG_KEEP_SCREEN_ON、释放网络状态但不主动销毁渲染 surface因此最后一帧停留在画面上。Demo 页面 KeepLastFrameDemoActivity.java 提供了一个开关可以在保留最后一帧与回到封面两种行为之间切换验证。需要特别强调这不是全局默认行为。在把它下沉到基础组件之前需要先确认业务侧的封面策略、Surface 释放策略和重播逻辑详见 doc/KEEP_LAST_FRAME_EN.md 与 doc/KEEP_LAST_FRAME.md。截图语义增强视频帧截图与组合截图保留不变的视频帧截图 API现有视频帧截图语义保持不变两个入口均在 StandardGSYVideoPlayer.java 中player.taskShotPic(listener); // 异步回调 Bitmap player.saveFrame(file, listener); // 保存到文件新增的组合截图 API组合截图用于把视频画面和播放器 UI 一起截下来的场景例如需要包含字幕 overlay、控制栏的截图player.taskShotPicWithView(listener); // 视频帧 播放器 UI player.saveFrameWithView(file, listener); // 组合截图保存文件从 StandardGSYVideoPlayer.java 的实现可以看到组合截图的完整链路taskShotPicWithView强制切回主线程执行先按播放器宽高创建目标 Bitmap默认RGB_565hightrue时用ARGB_8888调用内部taskShotPic先拿到视频帧把视频帧按渲染视图位置绘制到目标 Bitmap 上resolveRenderRect处理 SurfaceView 与 TextureView 的坐标差异再调用drawPlayerOverlay把播放器 UI控制栏、字幕等叠加绘制上去最后通过GSYVideoShotListener.getBitmap回调组合结果并回收中间 Bitmap。saveFrameWithView复用同一链路保存时以FileUtils.saveBitmapToFile的真实写入结果回调success布尔值。当前修复点SurfaceView 通过 PixelCopy 截图时所有失败路径都会回调null或successfalse不会静默吞掉错误TextureView、SurfaceView、GLSurfaceView 的保存回调现在反映真实文件写入结果组合截图先取视频帧、再叠加 UI 的绘制顺序保证字幕 overlay 与控制栏能正确显示在画面上。GLSurfaceView 效果与生命周期GL 相关整理集中在DetailFilterActivity与GSYVideoGLView*Render系列类上Demo 内统一切换到GSYVideoType.GLSURFACE渲染类型退出时恢复进入前的 render typeGSYVideoGLViewBaseRender/GSYVideoGLViewSimpleRender增强了 release 和截图回调降低 GL 生命周期异常导致的卡住风险滤镜Demo 可以切换普通滤镜、纹理水印、多窗口播放、图片穿孔mask、背景高斯等场景。GL 渲染基类位于 gsyVideoPlayer-java 的 render 目录自定义渲染示例见 app 的 effect 目录GSYVideoGLViewCustomRender系列可在此基础上实现自定义 shader 效果。多 URL 清晰度切换SmartPickVideoapp/src/main/java/com/example/gsyvideoplayer/video/SmartPickVideo.java仍然面向多个独立 URL的清晰度切换或下一集切换场景例如清晰度 URL、下一集 URL。需要明确这个方案不是 HLS/DASH 标准 ABR。它的核心机制是使用一个临时 manager预加载目标 URL等待目标就绪后把当前播放位置同步过去再提交切换。近期针对切换稳态的优化点包括位置不归零切换时记录最新播放位置避免跳转到目标后直接回到 0源码中的updateLastKnownChangePosition、isChangeTargetPositionReliable负责记录与校验位置可信度seek 误差阈值 重试 超时保护resolveStartChange解析切换目标retryTmpSeek在 seek 失败时按会话重试超时后放弃本次切换失败回退切换失败会回退到原播放并释放临时 manager不让其长期占用资源onError分支与临时 manager 释放逻辑setUp(ListSwitchVideoModel)支持按源位置切换applySourcePosition统一落地源位置变更。如果服务端可以提供 HLS master playlist 或 DASH MPD应优先使用下一节的 Exo 自适应清晰度方案而不是多 URL 手动切换。Exo 自适应清晰度EXO自适应清晰度DemoExoAdaptiveTrackActivity.java使用一个 HLS master URL 或一个 DASH MPD URL。Exo/Media3 会把同一个 track group 里的多码率 video track 用于 adaptive playback由 Media3TrackSelector在同一时间线内自动选择或固定指定 track。Demo 内置测试源HLS masterhttps://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8DASH MPDhttps://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd新增的对外 API 由GSYExoVideoManager暴露GSYExoVideoManager.java逐层转发到GSYExoPlayerManager再到GSYExo2MediaPlayerGSYExoVideoManager.instance().getVideoTrackInfoList(); // 列出可选视频轨 GSYExoVideoManager.instance().clearVideoTrackOverride(); // 清除固定选择恢复自适应 GSYExoVideoManager.instance().setVideoTrackOverride(groupIndex, trackIndex); // 固定到指定 track底层实现位于 GSYExo2MediaPlayer.javagetVideoTrackInfoList()遍历 Media3Tracks.Group只保留C.TRACK_TYPE_VIDEO类型的组为每个 track 生成VideoTrackInfo包含 groupIndex、trackIndex、format、是否被选中/是否支持/组是否支持自适应setVideoTrackOverride(groupIndex, trackIndex)校验组类型与 track 可支持性后构造TrackSelectionOverride并写入TrackSelectionParametersclearVideoTrackOverride()清除 video 类型的所有 override、解除禁用状态并重置强制最低/最高码率标记。使用语义默认自动由 TrackSelector 根据带宽、buffer、设备能力自动选择 track选择固定清晰度使用TrackSelectionOverride固定到某个 video track清除 override 后恢复自适应播放。播放器初始化失败安全处理播放器初始化加固集中在GSYVideoBaseManagerGSYVideoBaseManager.java和各内核的IPlayerManager实现中。当 IJK、System、Exo、AliPlayer 创建或初始化失败时流程会尽量走onError回调和资源释放清理而不是把异常直接抛到业务层导致 crash。GSYVideoBaseManager的onError(IMediaPlayer, int, int)会统一把 what/extra 转发给上层listener().onError让业务侧有机会进入错误态 UI。该能力没有单独 Demo 入口属于全局稳定性兜底。建议回归时覆盖非法 URL、缺失解码能力或故意构造异常初始化路径确认播放器进入错误态且应用不崩溃。Exo 缓存生命周期与 GIF 清理ExoSourceManager位于 gsyVideoPlayer-exo_player2 的 exo2 源码目录收紧了 Exo cache 的打开、复用和释放流程降低 cache 打开/复用/释放过程中的资源残留风险。GifCreateHelperGifCreateHelper.java在 GIF 生成结束、失败或取消后会更可靠地推进状态并清理临时资源——从源码看cancelTask()取消计时任务、clearTmpFiles()清理临时图片、release()统一收尾覆盖了三条退出路径。该能力同样没有单独 Demo 入口回归时应覆盖Exo cache 播放、退出页面、重新进入播放以及滤镜 Demo 里的 GIF 生成/失败/取消路径。DLNA/UPnP 投屏协议无关 SPI 留在核心jUPnP 实现由可选模块提供投屏的协议无关 SPI 保留在gsyVideoPlayer-javacast 目录从 13.2.1 起默认的 jUPnP 3.0.3 DLNAAVTransport:1实现由可选的gsyvideoplayer-cast模块提供dlna 目录 中的JupnpDlnaProvider与JupnpDlnaSession。SPI 三件套CastCapabilityCastCapability.java入口门面通过GSYVideoBaseManager.getCastCapability()获取CastProviderCastProvider.java具体协议 SPI由 App 注册到CastCapability默认 DLNA 实现是可选模块中的JupnpDlnaProviderCastSessionCastSession.java一次投屏会话生命周期setMediaItem(CastMediaInfo)→play/pause/stop/seekTo→release()状态与进度通过SessionListener回调。从 GSYVideoBaseManager.java 的注释可以确认首次访问getCastCapability()不会自动注册任何 providerApp 显式依赖gsyvideoplayer-cast后自行注册JupnpDlnaProvider。中途起播示例投屏的核心场景是从本地播放中途转投到大屏并保留进度。CastMediaInfo构造器CastMediaInfo.java除 url/title/mimeType 外还接收durationMs与startPositionMs其中 mimeType 会写入 DLNA 的 protocolInfo未知时长传 0 时位置轮询会用GetPositionInfo覆盖。完整流程CastCapability cast GSYVideoManager.instance().getCastCapability(); cast.registerProvider(new JupnpDlnaProvider()); cast.addListener(castListener); // onDeviceListChanged / onSessionStateChanged / onError cast.startDiscovery(context.getApplicationContext()); // 用户选中设备后 long localPositionMs videoPlayer.getCurrentPositionWhenPlaying(); CastMediaInfo media new CastMediaInfo(url, title, video/mp4, /*durationMs*/ 0L, localPositionMs); cast.connect(selectedDevice, new CastProvider.ConnectCallback() { Override public void onConnected(CastSession session) { session.addListener(sessionListener); session.setMediaItem(media); // SetAVTransportURI → Play → Seek(localPositionMs) } Override public void onError(Throwable error) { /* 展示连接错误 */ } }); // 页面退出或用户结束投屏先停止远端再释放会话与发现服务。 CastSession active cast.getActiveSession(); if (active ! null) active.stop(); cast.disconnect(); cast.stopDiscovery();Demo 与 Loopback 单机自测MainActivity提供独立的投屏 Demo入口CastDemoActivity包含 DLNA 设备选择列表与 Loopback Receiver 开关。演示播放器SampleCastControlVideo在投屏成功后自动塌陷成远端遥控 overlay本地 surface/audio 释放断开后按最近一次远端进度恢复本地播放。单机自测用的 Loopback Receiver无需真电视包含以下机制见 DevReceiverService.javaDevReceiverService运行在独立:dlna进程foreground service注册 jUPnPLocalDeviceurn:schemas-upnp-org:device:MediaRenderer:1并挂载LoopbackAvTransportServiceLoopbackRenderingControlService接收端CastReceiverFloatingWindow以 SYSTEM_ALERT_WINDOW 悬浮窗呈现远端播放画面CastReceiverPlayer内嵌 IJK 内核getPositionInfo/getTransportInfo回填真实进度与状态sender 端 1Hz 轮询看到的进度就是远端真实播放位置Service ↔ Activity 通过setPackage私有广播同步状态ACTION_STATE_READY/ACTION_STATE_STOPPED/ACTION_STATE_ERROR广播用显式包名锁定同 App无需声明 permissionAndroid 13 已适配RECEIVER_NOT_EXPORTED。依赖开关与版本前提需要 DLNA 时添加依赖io.github.carguo:gsyvideoplayer-cast:13.2.1该 artifact 统一传递 jUPnP/Jetty并声明真实的minSdk 26。默认播放器不含 Jetty最低版本保持 API 23详见 doc/DEPENDENCIES_EN.md / doc/DEPENDENCIES.md。更多能力目标与测试判据见 doc/CAST_FEATURE_PLAN.md 与 doc/CAST_TEST_PLAYBOOK.md。回归清单每次修改上述能力后至少执行构建与安装./gradlew :gsyVideoPlayer-java:testDebugUnitTest :app:assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk真机建议逐项检查打开VIDEO拖动进度条确认 WebVTT 预览图能出现自定义EXO支持字幕、通用字幕非EXO本地 SRT、本地 VTT、网络 SRT 都能切换失败不影响播放完成保留最后一帧打开/关闭 flag确认完成后分别停留最后一帧和回到封面滤镜切换滤镜和 GL 场景确认播放、截图、GIF 不崩无缝切换切换多个 URL确认不回 0失败可回退EXO自适应清晰度HLS 和 DASH 都能播放轨道列表能显示自动/固定清晰度能切换播放失败和内核初始化异常确认进入错误回调不直接 crashExo cache 和 GIF确认退出、重进、失败路径都能清理资源投屏 Demo可选择 DLNA 设备投屏后本地塌陷成遥控 overlay断开后本地在近似远端进度处继续启用Loopback Receiver可无真电视自测。小结本文沿 doc/RECENT_FEATURES_EN.md 的脉络逐项拆解了 GSYVideoPlayer 近期十个播放能力点WebVTT 进度预览的VTT 轨道 sprite 裁剪新范式、跨内核的 UI 层外挂字幕、保留最后一帧的 Demo 实现、视频帧/组合双层截图 API、GLSurfaceView 生命周期整理、多 URL 切换的位置与超时加固、Exo 自适应清晰度Media3TrackSelectionOverride、播放器初始化失败兜底、Exo 缓存与 GIF 资源清理以及 SPI 与 jUPnP 实现解耦的 DLNA 投屏与 Loopback 自测链路。每个能力都给出了 Demo 入口、核心 API 和源码位置配合文末回归清单可直接用于二次开发与版本升级后的稳定性验证。【免费下载链接】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),仅供参考