mcpField
A
audio-video

audio-video

github.com/dcl-regenesislabs开发1747 次安装21 周前更新
代码仓库

关于 audio-video

audio-video 是一项面向 Decentraland 场景开发的媒体技能,说明如何配置音效、音频流和视频播放。它涵盖实体声明、运行时控制、视频材质绑定及媒体事件处理。

主要功能

  1. 01配置本地音频与空间音效
  2. 02设置音频流播放
  3. 03将视频绑定到网格表面
  4. 04运行时控制播放与音量
  5. 05监听媒体播放状态

适用场景

  1. 01为场景添加点击音效
  2. 02制作背景音乐或电台流
  3. 03在屏幕或模型表面播放视频
  4. 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需要网格来显示视频

选择流程:

  1. 是本地音频文件吗?→ AudioSource
  2. 是流媒体 URL(电台、实时音频)吗?→ AudioStream
  3. 是视频内容吗?→ 在平面/网格上使用 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 })
如何推荐音频
  1. 阅读音频目录文件
  2. 搜索符合用户描述或主题的声音
  3. 推荐具体的声音并提供下载命令
  4. 将选定的声音下载到场景的 sounds/ 目录
  5. 在代码中使用本地路径引用它们

重要: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

评论

支持 Markdown