常见音乐软件的一起听功能设计与实现1

思路大纲

整体思路        

        前端使用全局变量来控制播放的音乐,无论是前后切换歌曲还是点播,都只是传递的内个全局音乐id而不是操作本身,调用方只需要去修改,当id变化,将触发websocket的同步逻辑,这应当使用监听实现。

业务模块

        分为websocket模块,播放模块还有主体业务模块

  1. websocket模块主要就是前端进行指令发送,进行操作同步。
  2. 播放模块主要由前端完成,使用全局变量维持,页面的唯一性,就是打开多个页面,得到的数据都一样,如qq音乐,当操作一个播放页面,另一个页面的歌曲也会发生变化,使用的就是全局变量,相当于单例,前端保存的roomId也是同理。
  3. 主体业务模块主要是通过redis维护房间(set集合),邀请逻辑,生成邀请链接逻辑,创建房间,销毁房间,连接其余两个模块。

补充

        在实现时,还有一些细节但重要的问题:

        不能只存储用户,所以不能使用set,因为使用set无法分清谁是房主,虽然权力相同,但是歌单要用房主的。这个其实有些瑕疵,之后再进行修改。再播放器页面应该拥有一个接口供双方选择自己的歌单。

        然后切换音乐的时候,使用歌单id和index进行切换,把两个变量全存储到全局变量里,可以解决现有所有问题。因为之前想法是通过musicid传递,但是缺陷太明显了,如果我现在的index为1,点击下一首,双方的音乐id都发生切换,但是index并没有发生切换,客人再点击上一首就切到了index为0的歌曲,这就导致比较糟糕的用户体验了。

        websocket逻辑和http不同,ws需要考虑通信双方的需求和功能实现,因为他们往往一个ws功能要分成两个实现,一个发送者的发,一个接受者的接收,并且如果这两者身份不同,就会变成4个实现,两个发和两个收。举个例子,需要完成客人加入房间,主人可以实时显示出客人的登录(由于主人一定先于客人今日房间,所以客人不需要接收主人的加入通知),然后设置一个加入房间的报文,加入房间这个逻辑就不需要前端发送了,因为可以直接在服务端的onOpen里发送给主人的客户端,需要判断当前open的账号是不是主人,如果是则不执行(为什么直接使用onOpen呢,因为onOpen的触发节点就是连接开始,与加入房间触发时间一致),前端拥有一个全局变量是otherUser当这个值不存在时,才可以被赋值(减少无用的重复赋值)。这是ws逻辑,然后对其优化,先在redis里获取另一个用户的数据,如果有就赋值,可以解决很多未知的问题,比如当主人和客人同时刷新,redis里已经有客人加入了,但是主人刷新好的时候,客人已经刷新好了,导致客人客户端引起的服务端onOpen发送的消息并未成功发送,就需要客户端接入,还有客人的主人信息加载也要去redis里拿,因为当客人在房间内时,主人必在房间内。

实现逻辑

        主体代码如下:

        

@OnMessage
    public void onMessage(String message, @PathParam("account") String account, @PathParam("roomId") String roomId) {

        // 0. 直接向房间号发送指令,然后对房间内所有人都执行相同操作
        // 获取房间内的所有用户
        SimpleTogetherMessage operationMessage = JSON.parseObject(message, ChangMusicMessage.class);

        // 对报文进行解析
        Integer type = operationMessage.getType();
        ChangMusicOperationData data = (ChangMusicOperationData) operationMessage.getData();
        if (type.equals(WebsocketConstants.OPERATION_CHANGE_MUSIC)) {
            // 是切换音乐操作,进行音乐同步,redis进行存储,可以改用redis监听降低耦合,这里直接同步写了 todo
            if (data.getMusicIndex() != null) redisTemplate.opsForHash().put(TOGETHER_ROOM_KEY + roomId, TOGETHER_HASH_CURRENT_MUSIC_INDEX_KEY, data.getMusicIndex());
            if (data.getMusicSheet() != null) redisTemplate.opsForHash().put(TOGETHER_ROOM_KEY + roomId, TOGETHER_HASH_CURRENT_MUSIC_SHEET_KEY, data.getMusicSheet());
        }


        // 对房间内所有用户的客户端发出报文,报文不变
        // 获取对应Session
        Session session = getSuitableSession();
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    private Session getSuitableSession() {
        if (isOwner) {
            String visitorAccount = (String) redisTemplate.opsForHash().get(TOGETHER_ROOM_KEY + roomId, TOGETHER_HASH_VISITOR_KEY);
            return onlineUsers.get(visitorAccount);
        } else {
            String ownerAccount = (String) redisTemplate.opsForHash().get(TOGETHER_ROOM_KEY + roomId, TOGETHER_HASH_OWNER_KEY);
            return onlineUsers.get(ownerAccount);
        }
    }

主要就是向另一个用户发送切换音乐的报文,在客户端:

websocket.onmessage = function (event) {
  const message = JSON.parse(event.data)
  console.log(message)
  // 开始进行处理
  if (message.type === 0) {
    // 这个是加入房间的报文,可以有个提示之类的
    ElMessage.success('你的好友已加入房间!')
    // 然后对otherUser进行修改
    if (!useStore().otherUser.exist) {
      useStore().otherUser = message.data
    }

  }
  else if (message.type === 1) {
    ElMessage.success('我接收到了')
    useStore().togetherRoomInfo.togetherPlayMusicIndex = message.data.musicIndex
    useStore().togetherRoomInfo.togetherPlayMusicSheet = message.data.musicSheet
  }

  ElMessage.info('收到消息对象:' + message)
  console.log('收到消息对象:', message)
}

websocket.onerror = function (event) {
  ElMessage.error('WebSocket出现错误:', event)
  // 可以在这里考虑重新连接的策略,比如延迟一段时间后重新尝试连接
}
websocket.onclose = function (event) {
  ElMessage.error('WebSocket关闭了')
}
// 然后去查看这个房间内有没有别人进来


// 监听 togetherPlayMusic 的变化
watch(() => useStore().togetherRoomInfo.togetherPlayMusicIndex, (newValue, oldValue) => {
  if (newValue === oldValue) return
  const data = {
      musicIndex: useStore().togetherRoomInfo.togetherPlayMusicIndex,
      musicSheet: useStore().togetherRoomInfo.togetherPlayMusicSheet
  }
  const message = {
    type: 1,
    data: data
  }

  if (websocket && websocket.readyState === WebSocket.OPEN) {
    console.log(JSON.stringify(message))
    websocket.send(JSON.stringify(message))
  } else {
    console.error('WebSocket已关闭')
  }

})

客户端分两块大体逻辑,向服务端发送消息和接收来自服务端的消息,发送逻辑:就是当index有变化时,就发送相应的报文信息,所有的歌曲变化都与index关联;接受逻辑:接收到切换音乐报文后,就进行全局变量的赋值(响应式),然后一个再向服务端发一个相同的报文,这个报文可以起一个ack的作用(目前没作用,增加一个扩展点,但是这个功能没必要这样,一致性和安全性会涉及心跳来保证)。最开始的发送端又发现传过来的index和自己现在的index相同,就return掉。

ui如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值