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 工程中以Wrapper(AndroidView 包装)与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.kt | AndroidView直接承载StandardGSYVideoPlayer/ 任意NormalGSYVideoPlayer子类,保留全部内置能力(全屏、手势、缓存、字幕、滤镜等) |
| Native(纯 Compose 控件) | GSYPlayerSurface.kt 中的rememberGSYPlayerController+GSYPlayerSurface,配合 GSYDefaultControls.kt | 只承载画面,控制层完全用 Compose 自绘,UI 可完全定制 |
| 通用工具 | LifecycleBridge.kt | LifecycleEffect { event -> ... }用于统一订阅宿主 Lifecycle 事件 |
两种模式的取舍非常明确:Wrapper 模式牺牲 UI 定制换取"零成本继承"——所有 Java 侧内置能力原样可用,适合快速接入;Native 模式用GSYPlayerController把状态/事件/控制命令响应式化,控制 UI 完全由 Compose 自绘,适合深度定制播放器界面的场景。两种模式共享同一套生命周期桥接策略。
二、Wrapper 模式 API 详解
2.1GSYVideoPlayerView:标准播放器包装
GSYVideoPlayerView是 Wrapper 模式的默认入口,内部通过AndroidView创建一个StandardGSYVideoPlayer实例(见 GSYVideoPlayerView.kt#L28-L36):
@Composable fun GSYVideoPlayerView( modifier: Modifier = Modifier, setUp: (StandardGSYVideoPlayer) -> Unit, setUpKey: Any? = null, autoReleaseOnDispose: Boolean = true, autoPauseResume: Boolean = true, onPlayerCreated: (StandardGSYVideoPlayer) -> Unit = {}, )参数语义如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modifier | Modifier | Modifier | 布局修饰符,通常Modifier.fillMaxWidth().aspectRatio(16f/9) |
setUp | (StandardGSYVideoPlayer) -> Unit | 必填 | 配置 player 的 lambda,等价于GSYVideoOptionBuilder.build(player) |
setUpKey | Any? | null | 非空时其变化会重新调用setUp(典型用法:传 url);为null时仅在 factory 阶段调用一次setUp |
autoReleaseOnDispose | Boolean | true | 离屏(离开 composition)时自动player.release() |
autoPauseResume | Boolean | true | 自动桥接 Lifecycle:ON_PAUSE → GSYVideoManager.onPause()、ON_RESUME → onResume() |
onPlayerCreated | (StandardGSYVideoPlayer) -> Unit | {} | 拿到 player 实例的回调,可用于持有引用 |
实现要点(源码级):setUpKey的去重逻辑非常关键。源码用lastAppliedKey单元素数组记录上一次已应用的 key,AndroidView的updateblock 中只有setUpKey != null && lastAppliedKey[0] != setUpKey时才重新调用setUp(GSYVideoPlayerView.kt#L56-L64)。这避免了两类问题:一是每次重组都触发builder.build导致播放重置;二是update拿到过期闭包。同时源码用rememberUpdatedState(setUp)保证update里始终拿到最新 lambda,避免闭包引用过期外部变量。
2.2GSYAnyVideoPlayerView<T>:任意子类通用包装
相比GSYVideoPlayerView,通用版多出一个factory参数(GSYAnyVideoPlayerView.kt#L26-L35):
@Composable 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 中的rememberGSYPlayerController:
@Composable fun rememberGSYPlayerController( url: String? = null, cacheWithPlay: Boolean = false, title: String = "", autoPlay: Boolean = false, autoPauseResume: Boolean = true, ): GSYPlayerController| 参数 | 默认值 | 说明 |
|---|---|---|
url | null | 可空;非空时内部LaunchedEffect触发controller.setUp(url, cacheWithPlay, title, autoPlay) |
cacheWithPlay | false | 边播边缓存 |
title | "" | 视频标题(透传给底层 player) |
autoPlay | false | attach 完成后是否自动startPlayLogic |
autoPauseResume | true | 自动订阅 Lifecycle:ON_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与GSYComposePlayer
GSYPlayerSurface只承载画面、不绘制任何控制 UI(GSYPlayerSurface.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返回的GSYPlayerController(GSYPlayerController.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)
此外还有一组直 setter(P1-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 状态读取:GSYPlayerSnapshot+StateFlow
播放状态由 GSYPlayerState.kt 定义,双通道暴露:
snapshot: State<GSYPlayerSnapshot>:Compose 友好的读取入口(val snap by controller.snapshot即可响应式消费);stateFlow: StateFlow<GSYPlayerSnapshot>:协程形态,适合在 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 事件订阅:GSYPlayerEvent+SharedFlow
边沿事件走events: SharedFlow<GSYPlayerEvent>(GSYPlayerEvent.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 逃生口:withHost
withHost(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()后返回null;block 内禁止调用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 次 Effect;LifecycleEffect一次拿到所有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(...),把原生那套"竖向左半屏调亮度 / 竖向右半屏调音量 / 横向调进度"完整迁移到 Compose:
Modifier .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 版要求"无状态 + 可定制 UI",dialog/浮层由调用方通过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 |
| 硬解 / MediaCodec | MediaCodecComposeActivity.kt |
| 悬浮小窗 | FloatingWindowComposeActivity.kt |
| 音频独立 | AudioOnlyComposeActivity.kt |
| 多类型混排 | MoreTypeComposeActivity.kt |
| 多窗口并行 | MultiWindowActivity.kt / MultiWindowParallelComposeActivity.kt |
| 本地文件 | LocalFileComposeActivity.kt |
| 自定义主题 | CustomControlsThemeComposeActivity.kt |
| WebView 详情 | 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 API;Native 模式的GSYDefaultControls则完全跟随 Compose 主题。自定义主题对照 CustomControlsThemeComposeActivity.kt。
九、依赖接入
gsyVideoPlayer-compose以独立 Android Library 形式存在(模块目录 gsyVideoPlayer-compose,含 gradle.properties 与 consumer-rules.pro)。它依赖 Java 侧核心模块gsyVideoPlayer-java(GSYVideoOptionBuilder、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 路线(rememberGSYPlayerController+GSYPlayerSurface+GSYDefaultControls)把状态、事件、控制命令全部响应式化,控制 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),仅供参考