游戏开黑交友中的深夜歌房娱乐互动功能实现

在游戏开黑交友场景中,深夜歌房是玩家在休息时段进行社交娱乐的重要功能。一个完善的深夜歌房系统不仅能满足玩家的娱乐需求,还能增强用户粘性和社交互动。

游戏开黑交友中的深夜歌房娱乐互动功能实现

一、需求分析与场景概述

1.1 场景特点

  • 深夜氛围:轻松、放松的社交环境
  • 语音互动:唱歌、聊天、游戏互动
  • 娱乐属性:点歌、K歌、音效处理、礼物互动
  • 社区氛围:建立亲密的社交关系

1.2 核心需求拆解

功能模块描述技术要点
语音连麦多人语音互动低延迟、高音质
点歌系统歌曲选择与排队歌曲库管理、排麦列表
K歌体验伴奏播放、歌词显示BGM混音、歌词同步
音效处理变声、混响、美声AI音效处理
互动功能点赞、礼物、弹幕实时消息推送
房间管理创建、加入、权限控制房间生命周期管理

1.3 技术指标要求

┌─────────────────────────────────────────────────────────┐
│                    核心性能指标                            │
├─────────────────┬───────────────────────────────────────┤
│  语音延迟        │  < 200ms(用户感知无延迟)              │
│  伴奏人声同步     │  < 50ms(K歌体验流畅)                │
│  支持人数        │  10+ 人同时在线连麦                    │
│  弱网抗丢包      │  30% 丢包仍可正常运行                  │
│  歌词同步精度     │  < 100ms(跟唱体验好)                 │
└─────────────────┴───────────────────────────────────────┘

二、技术选型:ZEGO 产品矩阵

基于上述需求,我们选择 ZEGO Express SDK + ZEGO ZIM SDK 实现在线深夜歌房功能。

2.1 核心产品组合

技术选型方案

产品SDK核心能力
ZEGO 实时音视频ZEGO Express SDK低延迟语音连麦、BGM混音、变声/混响音效、耳返监听、音频3A处理
ZEGO 即时通讯ZEGO ZIM SDK点歌请求与排麦通知、礼物/点赞消息推送、歌词同步消息、房间状态通知
在线 KTV 方案ZEGO KTV SDK歌曲库管理、歌词组件、评分功能、合唱方案

2.2 对比优势

对比项自建方案ZEGO 方案
开发周期3-4 个月2-3 周
音频质量需要专业音效调优内置专业音效处理
混音能力需要自研混音算法内置混音API
音效效果需要第三方音效库原生支持变声/混响
歌词同步需要自研同步机制内置歌词组件

三、架构设计

3.1 整体架构

┌─────────────────────────────────────────────────────────────────────┐
│                           客户端层                                    │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────────────────┐    │
│  │   歌房 App   │    │   歌房 App   │    │       歌房 App          │    │
│  │   (主唱)     │    │   (观众)     │    │       (观众)            │    │
│  └──────┬──────┘    └──────┬──────┘    └───────────┬─────────────┘    │
│         │                  │                       │                  │
│         ▼                  ▼                       ▼                  │
│  ┌─────────────────────────────────────────────────────────────────┐  │
│  │                     Express SDK                                │  │
│  │   · 语音推拉流  · BGM混音  · 音效处理  · 耳返监听               │  │
│  └─────────────────────────────────────────────────────────────────┘  │
│         │                  │                       │                  │
│         ▼                  ▼                       ▼                  │
│  ┌─────────────────────────────────────────────────────────────────┐  │
│  │                         ZIM SDK                                │  │
│  │        · 点歌信令  · 礼物消息  · 歌词同步  · 房间通知            │  │
│  └─────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│                          ZEGO 云服务                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────┐    │
│  │  媒体服务器   │    │  信令服务器   │    │     Token 鉴权        │    │
│  └──────────────┘    └──────────────┘    └──────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│                          开发者服务端                                │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────┐    │
│  │  歌曲库服务   │    │  排麦管理     │    │   礼物/点赞统计       │    │
│  └──────────────┘    └──────────────┘    └──────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘

3.2 核心流程

流程一:创建/加入歌房

用户进入 → 选择房间 → 获取Token → 加入RTC房间 → 加载房间状态

流程二:点歌与排麦

用户点歌 → 发送点歌请求 → 排麦队列更新 → 广播排麦列表 → 等待演唱

流程三:K歌体验

轮到用户 → 加载伴奏 → 开始播放 → 混音推流 → 歌词同步显示

四、核心功能实现

4.1 项目初始化

4.1.1 安装依赖

# 使用 npm 安装
npm install zego-express-engine-webrtc zego-zim-web

# 或使用 yarn
yarn add zego-express-engine-webrtc zego-zim-web

4.1.2 初始化配置

// config.js
export const ZEGO_CONFIG = {
  appID: 123456789,  // 替换为你的 ZEGO AppID
  server: 'wss://webrtc.zego.im:8282',
  zone: 'cn'  // 区域配置
};

4.2 房间管理模块

4.2.1 创建/加入房间

import { ZegoExpressEngine } from 'zego-express-engine-webrtc';

class RoomManager {
  constructor() {
    this.zegoEngine = new ZegoExpressEngine();
    this.currentRoomID = null;
  }

  /**
   * 创建歌房
   */
  async createRoom(roomID, userID) {
    const config = {
      maxMemberCount: 10,
      isUserStatusNotify: true
    };
    
    await this.zegoEngine.loginRoom(roomID, {
      userID: userID,
      userName: `歌手_${userID}`
    }, config);
    
    this.currentRoomID = roomID;
    console.log('歌房创建成功:', roomID);
  }

  /**
   * 加入歌房
   */
  async joinRoom(roomID, userID, token) {
    const config = {
      token: token,
      isUserStatusNotify: true
    };
    
    await this.zegoEngine.loginRoom(roomID, {
      userID: userID,
      userName: `观众_${userID}`
    }, config);
    
    this.currentRoomID = roomID;
    console.log('加入歌房成功:', roomID);
  }

  /**
   * 退出歌房
   */
  async leaveRoom() {
    if (this.currentRoomID) {
      await this.zegoEngine.logoutRoom(this.currentRoomID);
      this.currentRoomID = null;
      console.log('退出歌房');
    }
  }
}

export const roomManager = new RoomManager();

4.3 BGM 混音模块

4.3.1 加载并播放背景音乐

class BgmManager {
  constructor() {
    this.currentBgmPath = null;
    this.zegoEngine = zegoEngine;
  }
  
  /**
   * 加载背景音乐(伴奏)
   * @param {string} bgmPath - 伴奏文件路径
   */
  async loadBgm(bgmPath) {
    this.currentBgmPath = bgmPath;
    // 创建 audio 元素加载伴奏
    this.audioElement = document.createElement('audio');
    this.audioElement.src = bgmPath;
    this.audioElement.loop = false;
    
    await this.audioElement.load();
    console.log('伴奏加载成功:', bgmPath);
  }
  
  /**
   * 播放伴奏
   * @param {number} volume - 音量 0-100
   */
  playBgm(volume = 50) {
    if (!this.audioElement) return;
    
    this.audioElement.volume = volume / 100;
    
    // 设置混音:将伴奏混入麦克风音频流
    this.zegoEngine.enableAudioMixing({
      source: this.audioElement,
      enable: true,
      volume: volume
    });
    
    this.audioElement.play();
    console.log('伴奏开始播放');
  }
  
  /**
   * 暂停伴奏
   */
  pauseBgm() {
    if (this.audioElement) {
      this.audioElement.pause();
    }
  }
  
  /**
   * 停止伴奏
   */
  stopBgm() {
    if (this.audioElement) {
      this.zegoEngine.enableAudioMixing({ enable: false });
      this.audioElement.pause();
      this.audioElement.currentTime = 0;
    }
  }
}

export const bgmManager = new BgmManager();

4.3.2 麦克风与伴奏混音

/**
 * 设置麦克风与伴奏混音比例
 * @param {number} micVolume - 麦克风音量 0-100
 * @param {number} bgmVolume - 伴奏音量 0-100
 */
setMixVolume(micVolume, bgmVolume) {
  // 设置麦克风采集音量
  this.zegoEngine.setCaptureVolume(micVolume);
  
  // 设置推流音量(包含麦克风+音效)
  this.zegoEngine.setPublishVolume(100);
  
  // 更新伴奏音量
  if (this.audioElement) {
    this.audioElement.volume = bgmVolume / 100;
    this.zegoEngine.enableAudioMixing({ volume: bgmVolume });
  }
  
  console.log(`混音设置: 麦克风 ${micVolume}%, 伴奏 ${bgmVolume}%`);
}

4.4 音效处理模块

4.4.1 变声效果

class VoiceEffectManager {
  constructor() {
    this.zegoEngine = zegoEngine;
  }
  
  /**
   * 设置变声效果
   * @param {string} effectType - 效果类型
   */
  setVoiceEffect(effectType) {
    const effects = {
      'normal': { pitch: 0, timbre: 0 },
      'female': { pitch: 8, timbre: 4 },
      'male': { pitch: -8, timbre: -4 },
      'child': { pitch: 12, timbre: 8 },
      'robot': { pitch: 0, timbre: -10 },
      'ghost': { pitch: -12, timbre: 6 }
    };
    
    const effect = effects[effectType] || effects['normal'];
    
    // 设置自定义变声参数
    this.zegoEngine.setVoiceChangerParam(
      ZegoVoiceChangerPreset.Custom,
      effect
    );
    
    console.log('变声效果已设置:', effectType);
  }
  
  /**
   * 关闭变声
   */
  disableVoiceEffect() {
    this.zegoEngine.setVoiceChangerParam(ZegoVoiceChangerPreset.None, {});
  }
}

4.4.2 混响效果

/**
   * 设置混响效果
   * @param {string} reverbType - 混响类型
   */
  setReverbEffect(reverbType) {
    const reverbs = {
      'none': ZegoReverbPreset.None,
      'hall': ZegoReverbPreset.ConcertHall,     // 音乐厅
      'studio': ZegoReverbPreset.RecordingStudio, // 录音室
      'ktv': ZegoReverbPreset.KTV,             // KTV
      'enhanced_ktv': ZegoReverbPreset.EnhancedKTV, // 增强型KTV
      'concert': ZegoReverbPreset.VocalConcert // 演唱会
    };
    
    const preset = reverbs[reverbType] || reverbs['none'];
    this.zegoEngine.setReverbPreset(preset);
    
    console.log('混响效果已设置:', reverbType);
  }
  
  /**
   * 关闭混响
   */
  disableReverbEffect() {
    this.zegoEngine.setReverbPreset(ZegoReverbPreset.None);
  }
}

export const voiceEffectManager = new VoiceEffectManager();

4.4.3 耳返监听

class HeadphoneMonitor {
  constructor() {
    this.zegoEngine = zegoEngine;
    this.isEnabled = false;
  }
  
  /**
   * 开启耳返监听
   * @param {number} volume - 耳返音量 0-100
   */
  enable(volume = 50) {
    this.zegoEngine.enableHeadphoneMonitor(true);
    this.zegoEngine.setHeadphoneMonitorVolume(volume);
    this.isEnabled = true;
    console.log('耳返已开启');
  }
  
  /**
   * 关闭耳返监听
   */
  disable() {
    this.zegoEngine.enableHeadphoneMonitor(false);
    this.isEnabled = false;
    console.log('耳返已关闭');
  }
  
  /**
   * 设置耳返音量
   */
  setVolume(volume) {
    if (this.isEnabled) {
      this.zegoEngine.setHeadphoneMonitorVolume(volume);
    }
  }
}

export const headphoneMonitor = new HeadphoneMonitor();

4.5 点歌与排麦模块

4.5.1 点歌请求(ZIM消息)

import { ZIM } from 'zego-zim-web';

class SongRequestManager {
  constructor() {
    this.zim = null;
    this.songQueue = [];
    this.currentSinger = null;
  }
  
  /**
   * 初始化 ZIM
   */
  init(appID) {
    this.zim = ZIM.getInstance({ appID });
    
    // 监听房间消息
    this.zim.on('receiveRoomMessage', (zim, data) => {
      this.handleRoomMessage(data);
    });
  }
  
  /**
   * 登录 ZIM
   */
  async login(userID) {
    await this.zim.login({
      userID: userID,
      userName: `用户_${userID}`
    });
  }
  
  /**
   * 发送点歌请求
   * @param {string} roomID - 房间ID
   * @param {object} songInfo - 歌曲信息
   */
  async sendSongRequest(roomID, songInfo) {
    const message = JSON.stringify({
      type: 'song_request',
      song: songInfo,
      requester: this.currentUserID,
      timestamp: Date.now()
    });
    
    await this.zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    console.log('点歌请求已发送:', songInfo.songName);
  }
  
  /**
   * 处理房间消息
   */
  handleRoomMessage(data) {
    const message = JSON.parse(data.message);
    
    switch (message.type) {
      case 'song_request':
        this.handleSongRequest(message);
        break;
      case 'queue_update':
        this.updateQueue(message);
        break;
    }
  }
  
  /**
   * 处理点歌请求
   */
  handleSongRequest(message) {
    this.songQueue.push({
      song: message.song,
      requester: message.requester,
      status: 'waiting'
    });
    
    // 广播更新排麦列表
    this.broadcastQueueUpdate();
  }
  
  /**
   * 广播排麦队列更新
   */
  async broadcastQueueUpdate() {
    const message = JSON.stringify({
      type: 'queue_update',
      queue: this.songQueue,
      currentSinger: this.currentSinger,
      timestamp: Date.now()
    });
    
    await this.zim.sendRoomMessage(
      { type: 'message', message: message },
      this.currentRoomID
    );
  }
  
  /**
   * 更新队列显示
   */
  updateQueue(data) {
    this.songQueue = data.queue;
    this.currentSinger = data.currentSinger;
    
    // 更新UI显示排麦列表
    console.log('排麦队列更新:', this.songQueue);
  }
}

export const songRequestManager = new SongRequestManager();

4.6 歌词同步模块

4.6.1 歌词数据结构

// 歌词格式示例(KRC格式)
const lyrics = {
  songName: '夜曲',
  singer: '周杰伦',
  duration: 245, // 秒
  lines: [
    { time: 0, text: '一群嗜血的蚂蚁', phonetic: 'yi qun shi xue de ma yi' },
    { time: 3, text: '被腐肉所吸引', phonetic: 'bei fu rou suo xi yin' },
    { time: 6, text: '我面无表情', phonetic: 'wo mian wu biao qing' },
    { time: 9, text: '看孤独的风景', phonetic: 'kan gu du de feng jing' },
    // ... 更多歌词
  ]
};

// 歌词状态
const lyricState = {
  currentLineIndex: 0,      // 当前歌词行索引
  isPlaying: false,         // 是否正在播放
  playStartTime: 0,         // 播放开始时间
  progress: 0               // 当前播放进度(0-100)
};

4.6.2 歌词同步显示

class LyricManager {
  constructor() {
    this.currentLyrics = null;
    this.currentLineIndex = 0;
    this.playStartTime = 0;
    this.isPlaying = false;
    this.updateInterval = null;
  }
  
  /**
   * 加载歌词
   * @param {object} lyrics - 歌词数据
   */
  loadLyrics(lyrics) {
    this.currentLyrics = lyrics;
    this.currentLineIndex = 0;
  }
  
  /**
   * 开始同步歌词
   */
  startSync() {
    if (!this.currentLyrics) return;
    
    this.playStartTime = Date.now();
    this.isPlaying = true;
    this.updateLyric();
  }
  
  /**
   * 更新歌词显示
   */
  updateLyric() {
    if (!this.isPlaying || !this.currentLyrics) return;
    
    const elapsed = (Date.now() - this.playStartTime) / 1000;
    
    // 找到当前应该显示的歌词行
    for (let i = this.currentLineIndex; i < this.currentLyrics.lines.length; i++) {
      if (elapsed >= this.currentLyrics.lines[i].time) {
        this.currentLineIndex = i;
        
        // 通知UI更新歌词
        this.onLyricUpdate(this.currentLyrics.lines[i], i);
      }
    }
    
    // 继续更新
    if (this.currentLineIndex < this.currentLyrics.lines.length - 1) {
      requestAnimationFrame(() => this.updateLyric());
    }
  }
  
  /**
   * 歌词更新回调
   * @param {object} line - 当前歌词行
   * @param {number} index - 行索引
   */
  onLyricUpdate(line, index) {
    // 更新UI显示
    console.log(`[${index + 1}] ${line.text}`);
    
    // 可以触发事件通知UI组件
    if (this.onLyricChangeCallback) {
      this.onLyricChangeCallback({
        line: line,
        index: index,
        totalLines: this.currentLyrics.lines.length
      });
    }
  }
  
  /**
   * 暂停歌词同步
   */
  pauseSync() {
    this.isPlaying = false;
  }
  
  /**
   * 恢复歌词同步
   */
  resumeSync() {
    this.playStartTime = Date.now() - (this.currentLyrics.lines[this.currentLineIndex].time * 1000);
    this.isPlaying = true;
    this.updateLyric();
  }
  
  /**
   * 设置歌词更新回调
   */
  setOnLyricChange(callback) {
    this.onLyricChangeCallback = callback;
  }
}

export const lyricManager = new LyricManager();

4.7 互动功能模块

4.7.1 礼物与点赞

class InteractionManager {
  constructor() {
    this.zim = null;
    this.likeCount = 0;
  }
  
  /**
   * 初始化
   */
  init(zimInstance) {
    this.zim = zimInstance;
    
    // 监听互动消息
    this.zim.on('receiveRoomMessage', (zim, data) => {
      this.handleInteractionMessage(data);
    });
  }
  
  /**
   * 发送礼物
   * @param {string} roomID - 房间ID
   * @param {object} gift - 礼物信息
   */
  async sendGift(roomID, gift) {
    const message = JSON.stringify({
      type: 'gift',
      gift: gift,
      sender: this.currentUserID,
      timestamp: Date.now()
    });
    
    await this.zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    console.log(`发送礼物: ${gift.name}`);
  }
  
  /**
   * 发送点赞
   */
  async sendLike(roomID) {
    const message = JSON.stringify({
      type: 'like',
      sender: this.currentUserID,
      timestamp: Date.now()
    });
    
    await this.zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    this.likeCount++;
    console.log(`点赞成功,当前点赞数: ${this.likeCount}`);
  }
  
  /**
   * 处理互动消息
   */
  handleInteractionMessage(data) {
    const message = JSON.parse(data.message);
    
    switch (message.type) {
      case 'gift':
        this.onGiftReceived(message);
        break;
      case 'like':
        this.onLikeReceived(message);
        break;
    }
  }
  
  /**
   * 收到礼物
   */
  onGiftReceived(data) {
    console.log(`收到礼物: ${data.gift.name} 来自 ${data.sender}`);
    
    // 触发礼物动画
    if (this.onGiftCallback) {
      this.onGiftCallback(data);
    }
  }
  
  /**
   * 收到点赞
   */
  onLikeReceived(data) {
    this.likeCount++;
    console.log(`收到点赞来自: ${data.sender}, 总点赞数: ${this.likeCount}`);
    
    // 触发点赞动画
    if (this.onLikeCallback) {
      this.onLikeCallback(this.likeCount);
    }
  }
  
  /**
   * 设置回调
   */
  setOnGift(callback) {
    this.onGiftCallback = callback;
  }
  
  setOnLike(callback) {
    this.onLikeCallback = callback;
  }
}

export const interactionManager = new InteractionManager();

4.8 业务层整合

// KtvRoom.js - 歌房业务管理器

class KtvRoomManager {
  constructor() {
    this.currentUserID = null;
    this.currentRoomID = null;
    this.isSinging = false;
  }
  
  /**
   * 初始化歌房
   */
  async init(userID) {
    this.currentUserID = userID;
    
    // 初始化各个模块
    songRequestManager.init(ZEGO_CONFIG.appID);
    await songRequestManager.login(userID);
    
    // 设置互动回调
    interactionManager.init(songRequestManager.zim);
    interactionManager.setOnGift(this.handleGift.bind(this));
    interactionManager.setOnLike(this.handleLike.bind(this));
    
    console.log('歌房系统初始化完成');
  }
  
  /**
   * 创建歌房
   */
  async createRoom(roomID) {
    await roomManager.createRoom(roomID, this.currentUserID);
    this.currentRoomID = roomID;
    
    // 开启麦克风
    await this.enableMicrophone();
    
    console.log('歌房创建成功:', roomID);
  }
  
  /**
   * 加入歌房
   */
  async joinRoom(roomID, token) {
    await roomManager.joinRoom(roomID, this.currentUserID, token);
    this.currentRoomID = roomID;
    
    console.log('加入歌房成功:', roomID);
  }
  
  /**
   * 开始唱歌
   */
  async startSinging(songInfo) {
    try {
      this.isSinging = true;
      
      // 加载伴奏和歌词
      await bgmManager.loadBgm(songInfo.bgmUrl);
      lyricManager.loadLyrics(songInfo.lyrics);
      
      // 设置音效
      voiceEffectManager.setReverbEffect('ktv');
      headphoneMonitor.enable(50);
      
      // 开始播放伴奏和歌词
      bgmManager.playBgm(40);
      lyricManager.startSync();
      
      console.log(`开始演唱: ${songInfo.songName}`);
    } catch (error) {
      console.error('开始唱歌失败:', error);
      this.stopSinging();
    }
  }
  
  /**
   * 停止唱歌
   */
  async stopSinging() {
    this.isSinging = false;
    
    // 停止伴奏和歌词
    bgmManager.stopBgm();
    lyricManager.pauseSync();
    
    // 关闭音效
    voiceEffectManager.disableReverbEffect();
    headphoneMonitor.disable();
    
    console.log('停止演唱');
  }
  
  /**
   * 点歌
   */
  async requestSong(songInfo) {
    await songRequestManager.sendSongRequest(this.currentRoomID, songInfo);
    console.log(`点歌成功: ${songInfo.songName}`);
  }
  
  /**
   * 发送礼物
   */
  async sendGift(gift) {
    await interactionManager.sendGift(this.currentRoomID, gift);
  }
  
  /**
   * 发送点赞
   */
  async sendLike() {
    await interactionManager.sendLike(this.currentRoomID);
  }
  
  /**
   * 处理礼物消息
   */
  handleGift(data) {
    // 更新礼物动画和统计
    console.log(`收到礼物: ${data.gift.name}`);
  }
  
  /**
   * 处理点赞消息
   */
  handleLike(count) {
    // 更新点赞动画和统计
    console.log(`当前点赞数: ${count}`);
  }
  
  /**
   * 开启麦克风
   */
  async enableMicrophone() {
    await zegoEngine.startPublishing('');
    console.log('麦克风已开启');
  }
  
  /**
   * 退出歌房
   */
  async leaveRoom() {
    if (this.isSinging) {
      await this.stopSinging();
    }
    
    await roomManager.leaveRoom();
    this.currentRoomID = null;
    
    console.log('已退出歌房');
  }
}

export const ktvRoomManager = new KtvRoomManager();

五、Token 鉴权

5.1 为什么需要 Token?

Token 是用户登录 ZEGO 房间的钥匙,用于验证用户身份和权限。必须在服务端生成,切勿在客户端暴露密钥。

5.2 服务端 Token 生成(Node.js)

// token.js - 服务端 Token 生成
const { createCipheriv, randomBytes } = require('crypto');

function generateToken04(appID, userID, secret, effectiveTimeInSeconds, payload = '') {
  // 参数校验
  if (!appID || typeof appID !== 'number') throw new Error('appID 无效');
  if (!userID || typeof userID !== 'string') throw new Error('userID 无效');
  if (!secret || typeof secret !== 'string' || secret.length !== 32) throw new Error('secret 必须是 32 位');
  
  const VERSION_FLAG = '04';
  const createTime = Math.floor(Date.now() / 1000);
  
  const tokenInfo = {
    app_id: appID,
    user_id: userID,
    nonce: Math.floor(Math.random() * 2147483648),
    ctime: createTime,
    expire: createTime + effectiveTimeInSeconds,
    payload: payload || ''
  };

  // AES-GCM 加密
  const key = Buffer.from(secret, 'utf8');
  const nonce = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, nonce);
  const encrypted = Buffer.concat([cipher.update(JSON.stringify(tokenInfo), 'utf8'), cipher.final()]);
  const encryptBuf = Buffer.concat([encrypted, cipher.getAuthTag()]);

  // 拼接 Token
  const b1 = Buffer.alloc(8);
  const b2 = Buffer.alloc(2);
  const b3 = Buffer.alloc(2);
  
  b1.writeBigInt64BE(BigInt(tokenInfo.expire), 0);
  b2.writeUInt16BE(nonce.length, 0);
  b3.writeUInt16BE(encryptBuf.length, 0);

  const buf = Buffer.concat([b1, b2, nonce, b3, encryptBuf, Buffer.from([1])]);
  return VERSION_FLAG + buf.toString('base64');
}

// Express 路由示例
const express = require('express');
const app = express();

app.post('/api/getToken', (req, res) => {
  const { roomID, userID } = req.body;
  const appID = process.env.ZEGO_APP_ID;
  const secret = process.env.ZEGO_SECRET;
  
  const token = generateToken04(Number(appID), userID, secret, 7200);
  res.json({ token });
});

app.listen(3000, () => console.log('服务启动在 3000 端口'));

5.3 客户端获取 Token

/**
 * 从服务端获取 Token
 */
async function fetchToken(roomID, userID) {
  const response = await fetch('/api/getToken', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ roomID, userID })
  });

  if (!response.ok) throw new Error('获取 Token 失败');
  const data = await response.json();
  return data.token;
}

六、消息类型设计

6.1 消息类型一览

消息类型用途发送方核心字段
song_request点歌请求用户song, requester
queue_update排麦队列更新房主queue, currentSinger
gift礼物消息用户gift, sender
like点赞消息用户sender
lyric_sync歌词同步主唱lyrics, currentLine
room_status房间状态更新系统status, members

6.2 消息格式示例

// 点歌请求
{
  type: 'song_request',
  song: {
    id: 'song_001',
    songName: '夜曲',
    singer: '周杰伦',
    duration: 245,
    bgmUrl: 'https://xxx.com/bgm.mp3',
    lyricsUrl: 'https://xxx.com/lyrics.krc'
  },
  requester: 'user_123',
  timestamp: 1709012345678
}

// 礼物消息
{
  type: 'gift',
  gift: {
    id: 'gift_001',
    name: '玫瑰花',
    count: 1,
    icon: '🌹'
  },
  sender: 'user_456',
  timestamp: 1709012346000
}

// 歌词同步
{
  type: 'lyric_sync',
  currentLine: {
    time: 0,
    text: '一群嗜血的蚂蚁',
    phonetic: 'yi qun shi xue de ma yi'
  },
  lineIndex: 0,
  totalLines: 100
}

七、性能优化策略

7.1 音频质量优化

// 设置高质量音频配置
const audioConfig = {
  audioBitrate: 64,           // 提高码率到64kbps
  audioCodec: 'AAC',          // 使用AAC编码
  sampleRate: 48000,          // 48kHz采样率
  channelCount: 2             // 立体声
};

zegoEngine.setAudioConfig(audioConfig);

7.2 弱网适应

// 开启抗丢包和抖动缓冲
const rtcConfig = {
  enableFEC: true,             // 前向纠错
  enableJitterBuffer: true,    // 抖动缓冲
  jitterBufferMaxDelay: 150    // 最大延迟150ms
};

// 开启网络质量监控
zegoEngine.startQualityMonitor(3000);

// 监听网络质量变化
zegoEngine.on('qualityUpdate', (quality) => {
  const { rtt, packetLostRate } = quality;
  
  // 根据网络质量调整策略
  if (packetLostRate > 20) {
    // 弱网环境,降低码率
    zegoEngine.setAudioConfig({ audioBitrate: 32 });
  } else {
    // 恢复正常码率
    zegoEngine.setAudioConfig({ audioBitrate: 64 });
  }
});

7.3 资源预加载

class ResourcePreloader {
  constructor() {
    this.preloadedSongs = new Map();
  }
  
  /**
   * 预加载热门歌曲
   */
  async preloadPopularSongs() {
    const popularSongs = await this.fetchPopularSongs();
    
    for (const song of popularSongs) {
      await this.preloadSong(song);
    }
    
    console.log(`预加载完成,共 ${this.preloadedSongs.size} 首歌曲`);
  }
  
  /**
   * 预加载单首歌曲
   */
  async preloadSong(song) {
    const audio = new Audio();
    audio.src = song.bgmUrl;
    audio.preload = 'auto';
    
    await new Promise((resolve) => {
      audio.addEventListener('loadedmetadata', resolve);
      audio.load();
    });
    
    this.preloadedSongs.set(song.id, audio);
  }
  
  /**
   * 获取预加载的歌曲
   */
  getPreloadedSong(songId) {
    return this.preloadedSongs.get(songId);
  }
  
  /**
   * 获取热门歌曲列表
   */
  async fetchPopularSongs() {
    const response = await fetch('/api/popularSongs');
    return response.json();
  }
}

export const resourcePreloader = new ResourcePreloader();

八、安全与权限控制

8.1 房间权限管理

// 房间权限配置
const roomPrivilege = {
  joinRoom: 1,           // 0=禁止, 1=允许加入
  publishStream: 1,       // 0=禁止, 1=允许推流(唱歌)
  sendMsg: 1,             // 0=禁止, 1=允许发送消息
  admin: 0               // 0=普通用户, 1=管理员
};

// 在登录房间时传入权限配置
await zegoEngine.loginRoom(roomID, {
  userID: userID,
  userName: userName
}, {
  token: token,
  privilege: roomPrivilege
});

8.2 管理员权限

class RoomAdmin {
  /**
   * 禁言用户
   */
  async muteUser(roomID, userID) {
    const message = JSON.stringify({
      type: 'mute_user',
      targetUserID: userID,
      timestamp: Date.now()
    });
    
    await zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    console.log(`用户 ${userID} 已被禁言`);
  }
  
  /**
   * 解除禁言
   */
  async unmuteUser(roomID, userID) {
    const message = JSON.stringify({
      type: 'unmute_user',
      targetUserID: userID,
      timestamp: Date.now()
    });
    
    await zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
  }
  
  /**
   * 踢人
   */
  async kickUser(roomID, userID, reason = '') {
    const message = JSON.stringify({
      type: 'kick_user',
      targetUserID: userID,
      reason: reason,
      timestamp: Date.now()
    });
    
    await zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    console.log(`用户 ${userID} 已被踢出房间`);
  }
  
  /**
   * 关闭房间
   */
  async closeRoom(roomID) {
    const message = JSON.stringify({
      type: 'room_closed',
      reason: '房主关闭房间',
      timestamp: Date.now()
    });
    
    await zim.sendRoomMessage(
      { type: 'message', message: message },
      roomID
    );
    
    // 退出房间
    await roomManager.leaveRoom();
  }
}

九、总结

9.1 方案优势

优势项说明
专业音效内置变声、混响(KTV/音乐厅/录音室)等音效处理
低延迟全球 200+ 节点,语音延迟 < 200ms
快速集成SDK封装完善,2-3周即可上线
歌词同步内置歌词组件,同步精度 < 100ms
稳定可靠弱网 30% 丢包仍可正常运行

9.2 技术栈总结

┌─────────────────────────────────────────────────────────────┐
│                       技术架构总结                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   表现层                                                     │
│   ├── 歌房 UI(点歌、排麦、歌词显示)                          │
│   └── 互动组件(礼物、点赞、弹幕)                             │
│                                                             │
│   ─────────────────────────────────────────────────────────  │
│                                                             │
│   能力层                                                     │
│   ├── Express SDK(语音连麦、BGM混音、音效处理)              │
│   ├── ZIM SDK(点歌信令、互动消息)                           │
│   └── KTV 方案(歌曲库、歌词组件)                            │
│                                                             │
│   ─────────────────────────────────────────────────────────  │
│                                                             │
│   支撑层                                                     │
│   ├── ZEGO 云服务(媒体处理、信令路由)                       │
│   └── 开发者服务端(歌曲库、排麦管理、礼物统计)               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

9.3 扩展方向

  1. 在线评分:接入唱歌评分算法,实时评分
  2. 合唱功能:支持多人实时合唱
  3. 短视频录制:录制演唱片段分享到社交平台
  4. 社交互动:关注、私信、好友系统
  5. 抢唱模式:增加游戏化互动玩法

附录

A. 相关资源

B. 混响预设值

预设值描述适用场景
NONE无混响普通语音
KTVKTV效果音色瑕疵较明显
ENHANCED_KTV增强型KTV普通用户/专业用户
CONCERT_HALL音乐厅追求空间感
RECORDING_STUDIO录音室纯净人声
VOCAL_CONCERT演唱会宏大效果

C. 常见问题

Q: 如何解决伴奏与人声不同步的问题?

A: 确保使用同一时钟源,开启 NTP 时间同步。在播放伴奏时记录开始时间,歌词同步使用相同的时间戳。

Q: 如何处理歌词延迟显示?

A: 可以提前 100ms 显示歌词,给用户预留反应时间。同时监听播放进度,动态调整同步精度。

Q: 多人连麦时如何保证音质?

A: 使用分层编码,根据网络质量动态调整码率。开启 AI 降噪和回声消除功能。

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/yinshipin/67583.html

(0)

相关推荐