关于 audio-video
audio-video 是一项面向 Decentraland 场景开发的媒体技能,说明如何配置音效、音频流和视频播放。它涵盖实体声明、运行时控制、视频材质绑定及媒体事件处理。
主要功能
- 01配置本地音频与空间音效
- 02设置音频流播放
- 03将视频绑定到网格表面
- 04运行时控制播放与音量
- 05监听媒体播放状态
适用场景
- 01为场景添加点击音效
- 02制作背景音乐或电台流
- 03在屏幕或模型表面播放视频
- 04响应音视频播放状态变化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill audio-video安装后在 Claude Code 中输入/audio-video确认已加载。
附带文件references/media-reference.md
详细介绍
Decentraland 中的音频和视频
作者职责划分
AudioSource(本地音频文件)、AudioStream(流媒体 URL)和VideoPlayer均在main-entities.ts中受支持——在此完整声明扬声器/收音机/屏幕实体及其流媒体/播放配置。- 运行时的音量/播放/暂停切换通过
src/index.ts中的getMutable完成。
何时使用哪种媒体组件
| 需求 | 组件 | 主要区别 |
|---|---|---|
| 播放文件中的音效(点击、爆炸、脚步声) | AudioSource | 本地文件、空间音频、单次播放或循环播放 |
| 背景音乐或电台流 | AudioStream | 外部 URL、非空间音频、连续播放 |
| 在表面播放视频(屏幕、广告牌) | VideoPlayer + Material.Texture.Video | 需要网格来显示视频 |
选择流程:
- 是本地音频文件吗?→
AudioSource - 是流媒体 URL(电台、实时音频)吗?→
AudioStream - 是视频内容吗?→ 在平面/网格上使用
VideoPlayer
音频源(音效和音乐)
在 main-entities.ts 中声明扬声器:
// main-entities.ts
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
speaker: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
AudioSource: {
audioClipUrl: 'sounds/music.mp3',
playing: true,
loop: true,
volume: 0.5, // 0 to 1
pitch: 1.0 // Playback speed (0.5 = half speed, 2.0 = double)
}
}
}
} satisfies Scene
支持的格式
.mp3(推荐).ogg.wav
空间音频与非空间音频
AudioSource 默认使用空间音频(音量会随距离增加而衰减)。对于背景音乐/电台/非定位音效,请设置 global: true:
// main-entities.ts
bg_music: {
components: {
Transform: { position: { x: 0, y: 0, z: 0 } }, // ignored when global
AudioSource: {
audioClipUrl: 'sounds/bg.mp3',
playing: true,
loop: true,
volume: 0.5,
global: true // heard everywhere in the scene at constant volume
}
}
}
文件组织
project/
├── sounds/
│ ├── click.mp3
│ ├── background-music.mp3
│ └── explosion.ogg
├── src/
│ └── index.ts
└── scene.json
播放/停止/切换(运行时,在 src/index.ts 中)
import { engine, AudioSource } from '@dcl/sdk/ecs'
export function main() {
const speaker = engine.getEntityOrNullByName('speaker')
if (!speaker) return
AudioSource.getMutable(speaker).playing = true // play
AudioSource.getMutable(speaker).playing = false // stop
// toggle
const audio = AudioSource.getMutable(speaker)
audio.playing = !audio.playing
}
点击播放
静态实体(按钮网格和点击音效扬声器)放在 main-entities.ts 中。PointerEvents 和点击处理函数属于运行时逻辑,放在 src/index.ts 中。
// main-entities.ts
sfx_button: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
}
},
click_sfx: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
AudioSource: {
audioClipUrl: 'sounds/click.mp3',
playing: false,
loop: false,
volume: 0.8
}
}
}
// src/index.ts
import { engine, AudioSource, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
export function main() {
const button = engine.getEntityOrNullByName('sfx_button')
const sfx = engine.getEntityOrNullByName('click_sfx')
if (!button || !sfx) return
pointerEventsSystem.onPointerDown(
{ entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Play sound' } },
() => {
// Reset and play
const audio = AudioSource.getMutable(sfx)
audio.playing = false
audio.playing = true
}
)
}
音频流
AudioStream 在 main-entities.ts 中受支持——在同一处声明收音机实体及其流媒体配置:
// main-entities.ts
radio: {
components: {
Transform: { position: { x: 8, y: 1, z: 8 } },
GltfContainer: { src: 'models/radio.glb' },
AudioStream: {
url: 'https://example.com/stream.mp3',
playing: true,
volume: 0.3
}
}
}
运行时切换播放状态/音量时,使用与 AudioSource 相同的 getMutable 模式。
视频播放器
VideoPlayer、MeshRenderer 和屏幕的 Transform 都放在 main-entities.ts 中。Material 中的视频纹理绑定需要运行时 Entity ID,而不是名称——构建过程只会按名称解析 Transform.parent。因此,要在 src/index.ts 中运行时设置 Material:
// main-entities.ts
video_screen: {
components: {
Transform: {
position: { x: 8, y: 3, z: 15.9 },
scale: { x: 8, y: 4.5, z: 1 } // 16:9 ratio
},
MeshRenderer: { mesh: { $case: 'plane', plane: { uvs: [] } } },
VideoPlayer: {
src: 'https://example.com/video.mp4',
playing: true,
loop: true,
volume: 0.5,
playbackRate: 1.0,
position: 0 // start time in seconds
}
}
}
// src/index.ts
import { engine, Material } from '@dcl/sdk/ecs'
export function main() {
const screen = engine.getEntityOrNullByName('video_screen')
if (!screen) return
const videoTexture = Material.Texture.Video({ videoPlayerEntity: screen })
// Basic material — better performance than PBR for video surfaces
Material.setBasicMaterial(screen, { texture: videoTexture })
}
视频控制
// Play
VideoPlayer.getMutable(screen).playing = true
// Pause
VideoPlayer.getMutable(screen).playing = false
// Change volume
VideoPlayer.getMutable(screen).volume = 0.8
// Change source
VideoPlayer.getMutable(screen).src = 'https://example.com/other.mp4'
增强型视频材质(PBR)
如需更明亮、带自发光效果的视频屏幕:
import { Color3 } from '@dcl/sdk/math'
const videoTexture = Material.Texture.Video({ videoPlayerEntity: screen })
Material.setPbrMaterial(screen, {
texture: videoTexture,
roughness: 1.0,
specularIntensity: 0,
metallic: 0,
emissiveTexture: videoTexture,
emissiveIntensity: 0.6,
emissiveColor: Color3.White()
})
在 GLTF 表面播放视频(曲面屏、电视、显示器)
当“屏幕”是模型的一部分(例如客厅场景中的电视、弧形竞技场显示屏)时,保留 GLTF 模型,并在运行时通过 GltfNodeModifiers 用视频纹理覆盖其屏幕材质:
// main-entities.ts — declare the TV model
tv: {
components: {
Transform: { position: { x: 8, y: 1.5, z: 8 } },
GltfContainer: { src: 'models/tv.glb' },
VideoPlayer: { src: 'https://example.com/show.mp4', playing: true, loop: true }
}
}
// src/index.ts — bind the video texture to the screen sub-mesh by path
import { engine, Material, GltfNodeModifiers } from '@dcl/sdk/ecs'
export function main() {
const tv = engine.getEntityOrNullByName('tv')
if (!tv) return
const videoTexture = Material.Texture.Video({ videoPlayerEntity: tv })
GltfNodeModifiers.createOrReplace(tv, {
modifiers: [
{
path: 'TV/Screen', // GLTF node path to the screen sub-mesh
material: {
material: {
$case: 'unlit',
unlit: { texture: videoTexture }
}
}
}
]
})
}
使用 path: ''(空字符串)将视频材质应用于模型的所有节点——当整个模型都是屏幕时(例如从 Blender 导出的平面广告牌网格),这非常有用。
视频事件
监控视频播放状态:
import { videoEventsSystem, VideoState } from '@dcl/sdk/ecs'
videoEventsSystem.registerVideoEventsEntity(screen, (videoEvent) => {
switch (videoEvent.state) {
case VideoState.VS_PLAYING:
console.log('Video started playing')
break
case VideoState.VS_PAUSED:
console.log('Video paused')
break
case VideoState.VS_READY:
console.log('Video ready to play')
break
case VideoState.VS_ERROR:
console.log('Video error occurred')
break
}
})
空间音频
Decentraland 中的音频默认采用空间音频——玩家靠近音频源实体时,音量会增大;远离时,音量会减小。位置由实体的 Transform 决定。
若要让音频不受空间影响(各处音量相同),目前没有内置标志可用——请保持较低音量,并将音频放在场景中心。
免费音频文件
创建占位音频文件引用之前,请务必先查看音频目录。其中包含来自 Creator Hub 资源包的 50 种免费音效。
阅读 {baseDir}/../../context/audio-catalog.md,了解音乐曲目(环境音乐、舞曲、中世纪、科幻等)、环境音效(鸟鸣、城市、工厂等)、交互音效(按钮、门、杠杆、宝箱)、音效(爆炸、警笛、铃声)和游戏机制音效(胜利/失败、治疗、重生、受伤)。
使用目录中的音效:
# Download from catalog
mkdir -p sounds
curl -o sounds/ambient_1.mp3 "https://builder-items.decentraland.org/contents/bafybeic4faewxkdqx67dloyw57ikgaeibc2e2dbx34hwjubl3gfvs2r4su"
// Reference in code — must be a local file path
AudioSource.create(entity, { audioClipUrl: 'sounds/ambient_1.mp3', playing: true, loop: true })
如何推荐音频
- 阅读音频目录文件
- 搜索符合用户描述或主题的声音
- 推荐具体的声音并提供下载命令
- 将选定的声音下载到场景的
sounds/目录 - 在代码中使用本地路径引用它们
重要:
AudioSource仅适用于本地文件。切勿在audioClipUrl字段中使用外部 URL。务必先将音频下载到sounds/。
视频状态轮询
以编程方式检查视频播放状态:
import { videoEventsSystem, VideoState } from '@dcl/sdk/ecs'
engine.addSystem(() => {
const state = videoEventsSystem.getVideoState(videoEntity)
if (state) {
console.log('Video state:', state.state) // VideoState.VS_PLAYING, VS_PAUSED, etc.
console.log('Current time:', state.currentOffset)
}
})
音频播放事件
使用 AudioEvent 组件检测音频状态变化:
import { AudioEvent } from '@dcl/sdk/ecs'
engine.addSystem(() => {
const event = AudioEvent.getOrNull(audioEntity)
if (event) {
console.log('Audio state:', event.state) // playing, paused, finished
}
})
外部媒体权限
外部音频/视频 URL 需要在 scene.json 中设置 ALLOW_MEDIA_HOSTNAMES 权限:
{
"requiredPermissions": ["ALLOW_MEDIA_HOSTNAMES"],
"allowedMediaHostnames": ["stream.example.com", "cdn.example.com"]
}
多个视频表面
通过引用同一个 videoPlayerEntity,让多个屏幕共用一个 VideoPlayer:
Material.setPbrMaterial(screen1, {
texture: Material.Texture.Video({ videoPlayerEntity: videoEntity })
})
Material.setPbrMaterial(screen2, {
texture: Material.Texture.Video({ videoPlayerEntity: videoEntity })
})
视频限制与提示
- 同时播放的视频:预览中 1 个,Explorer 中 5 个,整个场景最多 10 个
- 基于距离的控制:玩家距离较远时暂停视频,以节省带宽
- 支持的格式:
.mp4(H.264)、.webm,以及用于直播的 HLS(.m3u8) - 直播:使用 HLS(
.m3u8)URL——在不同客户端上的兼容性最可靠
有关组件字段的完整详情、支持的格式和高级用法,请参阅 {baseDir}/references/media-reference.md。
重要说明
- 音频文件必须位于项目目录中(相对于项目根目录的路径)
- 视频需要使用 HTTPS URL——HTTP 无法使用
- 播放器必须先与场景交互(点击),音频才能播放(浏览器自动播放策略)
- 音频文件应保持较小——大文件会延长场景加载时间
- 音乐使用
.mp3,音效使用.ogg(文件更小) - 直播视频尽可能使用 HLS(.m3u8)URL