直播间发送弹幕【使用beego和阿里云】

  • Web端

  • 简介
1.通过阿里云组件实现视频点播
2.通过阿里云组件实现视频点播弹幕功能
3.通过websocket组件实现弹幕内容发送到服务端
4.通过websocket监听端口,获取弹幕消息,并展示到视频中

后端直播间弹幕连接代码:

  • 实现视频弹幕的发送和接收
  • 将http请求升级成websocket请求
/*
   1. 设置http请求升级成websocket请求的配置信息
   2. 将http请求升级成websocket请求,并且返回连接信息
*/
import (
beego "github.com/beego/beego/v2/server/web"
"github.com/gorilla/websocket"
"github.com/zeromicro/go-zero/core/logx"
"io"
"log"
"net/http"
"os"
"strconv"
"video/global"
"video/models/mysql"
"video/pkg")

type ChatController struct {
beego.Controller}

func (c *ChatController) LiveChatLog() {
userId, _ := c.GetInt("user_id")
liveId, _ := c.GetInt("live_id")

log.Println("user_id", userId, "live_id", liveId)

upgrader := websocket.Upgrader{
   ReadBufferSize:  1024,
   WriteBufferSize: 1024,
   CheckOrigin: func(r *http.Request) bool {
      return true
   },
}

conn, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
if err != nil {
   return
}

var maps map[int]global.Node = make(map[int]global.Node)

maps[userId] = global.Node{
   Conn: conn,
   Data: make(chan []byte, 50),
}
//将用户连接信息放到直播间中
global.LiveClientMap[liveId] = append(global.LiveClientMap[liveId], maps)

global.Wg.Add(2)

go pkg.ReadLive(maps[userId])
go pkg.WriteLive(maps[userId])

global.Wg.Wait()
}
  • 将直播间消息发送到服务端:
package pkg

import (
"encoding/json"
"github.com/gorilla/websocket"
"log"
"video/global"
)

func ReadLive(node global.Node) {
defer global.Wg.Done()
for {
   _, message, err := node.Conn.ReadMessage()
   if err != nil {
      log.Println("消息读取失败", err)
      return
   }

   var msg global.Message

   err = json.Unmarshal(message, &msg)
   log.Println("msg.cmd", msg.Cmd)
   if err != nil {

      msg = global.Message{

         UserId: msg.UserId,

         LiveId: msg.UserId,

         Content: "结构体转换失败",
      }

      marshal, _ := json.Marshal(msg)
      node.Conn.WriteMessage(websocket.TextMessage, marshal)
      return
   }

   switch msg.Cmd {
   case 1:
      //循环直播间
      for _, u := range global.LiveClientMap[msg.LiveId] {
         //循环直播间中的每一个用户  将消息弹幕消息放入通道
         for _, n := range u {
         //将消息放入通道中
            n.Data <- message
         }
      }
   default:
      node.Conn.WriteMessage(websocket.TextMessage, []byte("消息类型错误"))
   }
}
}
func WriteLive(node global.Node) {
defer global.Wg.Done()
for {
   select {
   case data, ok := <-node.Data:
        //监听通道里面是否有值
      if ok {
         node.Conn.WriteMessage(websocket.TextMessage, data)
      }
    }
}
}
  • 将图片或者视频上传到阿里云:
package pkg

import (
"fmt"

"github.com/aliyun/aliyun-oss-go-sdk/oss"
"log"
"mime/multipart"
"os"
)

func UploadAliYun(filename string, file multipart.File) string {
accessKeyID := "LTAI5tLNL5i8CpPZMXg34TaB"
accessKeySecret := "9qQiYqCStfoLPMMesyYvDWQeN4SKWH"
client, err := oss.New("oss-cn-shanghai.aliyuncs.com", accessKeyID, accessKeySecret)
if err != nil {
   fmt.Println("Error2:", err)
   os.Exit(-1)
}

// 填写存储空间名称,例如examplebucket。
bucket, err := client.Bucket("lululucky")
if err != nil {
   fmt.Println("Error3:", err)
   os.Exit(-1)
}

// 依次填写Object的完整路径(例如exampledir/exampleobject.txt)和本地文件的完整路径(例如D:\\localpath\\examplefile.txt)。

err = bucket.PutObject(filename, file)

url, err := bucket.SignURL(filename, "post", 0)
if err != nil {
   return ""
}

log.Println("url", url)
return url
}
  • 控制器上传方法:
//上传视频

func (c *ChatController) UploadVideo()  {

//接收参数

video_title := c.GetString("video_title")

category, _ := c.GetInt("category")

user_id, _ := c.GetInt("user_id")

cmd, _ := c.GetInt("cmd")

file, m, err := c.GetFile("video")

if err != nil {

   return

}
//后缀名
ext := filepath.Ext(m.Filename)
//m.Size的单位是字节  1KB=1024字节  判断上传的文件大小及类型
if ext != ".mp4" || m.Size > 1024*1024*1024*10 {
   c.Data["json"] = map[string]interface{}{
      "code":    -1,
      "message": "文件类型不正确或者文件过大",
   }
   c.ServeJSON()
   return
}

var url string

switch cmd {
//将文件上传到本地
case 1:

   //定义文件上传的路径  根据用户id创建不同的文件夹
   uid := strconv.Itoa(user_id)

   path := "./static/video/" + uid + "/"

   filename := path + m.Filename

   //创建文件夹  并且设置权限
   os.MkdirAll(path, 0777)

   openFile, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777)

   if err != nil {
      logx.Error("图片上传失败", err)
      return
   }

   io.Copy(openFile, file)

case 2:
   //上传到阿里云oss
   url = pkg.UploadAliYun(m.Filename, file)
}

//上传到数据库
video := mysql.UploadVideo{
   UserId:     user_id,
   VideoTitle: video_title,
   Category:   category,
   VideoUrl:   url,
}

db := video.Upload()

if db.RowsAffected == 0 || db.Error != nil {

   c.Data["json"] = map[string]interface{}{

      "code":    -1,
      "message": "文件上传失败",
   }
   c.ServeJSON()

   return
}

var detail = mysql.VideoDetail{
   VideoId:    video.ID,
   VideoTitle: video_title,
   Comment:    "这个视频真好",
   VideoUrl:   url,
   Category:   category,
}

db = detail.CreateVideoDetail()

if db.RowsAffected < 1 || db.Error != nil {
   c.Data["json"] = map[string]interface{}{
      "code":    -1,
      "message": "视频详情创建失败",
   }
   c.ServeJSON()
   return
}

c.Data["json"] = map[string]interface{}{
   "code":    0,
   "message": "图片上传成功",
}
c.ServeJSON()
}
  • 列表展示:
// 获取视频列表
func (c *UploadVideo) GetVideoList(videoTitle string, page int, size int) (db *gorm.DB, videoList []*UploadVideo, count int64) {
db = Db
//计算偏移量
offset := (page - 1) * size
//根据视频标题查询
db = db.Table("upload_videos").Where("video_title like ?", "%"+videoTitle+"%").Count(&count)
//计算总页数
db = db.Table("upload_videos").Where("video_title like ?", "%"+videoTitle+"%").Offset(offset).Limit(size).Find(&videoList)

return

}
  • 控制器获取视频列表方法:
// 视频列表
func (c *ChatController) VideoList() {

videoTitle := c.GetString("video_title")
//当前页
page, _ := c.GetInt("page")

if page == 0 {

   c.Data["json"] = map[string]interface{}{

      "code":    -1,
      "message": "参数错误",
   }

   c.ServeJSON()

   return
}

//每页显示条数

var size = 5

var video mysql.UploadVideo

db, videoList, count := video.GetVideoList(videoTitle, page, size)

if db.RowsAffected < 1 || db.Error != nil {

   c.Data["json"] = map[string]interface{}{

      "code":    -1,
      "message": "视频列表查询失败",
   }
   c.ServeJSON()

   return
}

//计算总页数
sum := int(count) / size

var pages []int

for i := 1; i <= sum; i++ {

   pages = append(pages, i)

}

c.Data["json"] = map[string]interface{}{
   "code":       0,
   "message":    "视频列表查询成功",
   "video_list": videoList,
   "count":      count,
   "pages":      pages,
   "sum":        sum,
}
c.ServeJSON()
}
  • 前端代码:
分页,页面跳转
<template>
    <view class="content">
        <div>
            <table>
                <tr>
                    <td>视频列表</td>
                </tr>
                <tr>
                    <td>搜索条件</td>
                    <td><input type="text" v-model="video_title" placeholder="请输入视频名称" /></td>
                    <td><button  @click="GetVideoList(page)">搜索</button></td>
                </tr>
                <tr>
                    <td>视频id</td>
                    <td>发布者</td>
                    <td>视频标题</td>
                    <td>视频</td>
                </tr>
                <tr v-for="(item,index) in video_list">
                    <td>{{item.ID}}</td>
                    <td>{{item.UserId}}</td>
                    <td>{{item.VideoTitle}}</td>
                    <td @click="GetVideoDetail(item.ID)"><image :src="item.VideoUrl"  ></image></td>
                    </tr>
            </table>


        <a v-for="(item,index) in pages" >
            <button @click="GetVideoList(item)">{{item}}</button>

        </a>
        </div>

    </view>

</template>

<script>
    export default {
        data() {
            return {
                video_title: "",
                video_list: [],
                page: 1,
                pages: [],
                video_id: "",

            }
        },
        onLoad() {
            this.GetVideoList(1)
        },
        methods: {
            GetVideoList(p){
                uni.request({
                    url: 'http://127.0.0.1:8081/video/list', //仅为示例,并非真实接口地址。
                    method:'GET',
                    data: {
                        video_title: this.video_title,
                        page:p
                    },
                    success: (res) => {
                        console.log(res.data);

                        this.video_list=res.data.video_list

                        this.pages=res.data.pages

                        console.log("page",this.page)

                        console.log("p",p)

                    }
                });
            },//获取视频列表
            GetVideoDetail(id){
                uni.navigateTo({
                    url:"../detail/detail"
                })
            }
            //获取视频详情
        }
    }
</script>

<style>

</style>


视频播放:
<template>
    <view>
        <div id="player-con"></div>
        <input type="text" placeholder="请输入弹幕内容"v-model="content" />
        <button @click="sub()">发送</button>

    </view>
</template>

<script>
    import '@/static/js/aliplayer-min.js'
    import '@/static/js/aliplayercomponents-1.0.9.min.js'
    export default {
        data() {
            return {
                danmukuList:[],
                url: "",
                video_id: "",
                content: "",
                user_id:1,
                live_id:1,
                cmd:1,
                content:"",
                types: 1,

            }
        },
        mounted() {
            this.websocketConn()
            this.Get()

        },
        methods: {
            websocketConn(){
                uni.connectSocket({
                    url: 'ws://127.0.0.1:8081/video/chat?user_id='+this.user_id+'&live_id='+this.live_id,
                    method: 'GET'
                });

                uni.onSocketOpen(function (res) {
                  console.log('WebSocket连接已打开!');
                });

                uni.onSocketError(function (res) {
                  console.log('WebSocket连接打开失败,请检查!弹幕连接失败');
                });

                var self = this
                // 监听WebSocket接受到服务器的消息事件
                uni.onSocketMessage(function (res) {

                    console.log(JSON.parse(res.data))

                 var  contetnt = JSON.parse(res.data)

                  self.danmukuList.push({
                      mode:1,
                      text:contetnt.content,
                      stime:1e3,
                      size:25,
                      color:16777215
                  })
                });

            },//websocket连接
            sub(){

                //发起请求,请求弹幕存储接口
                var stime = document.getElementsByClassName("current-time")[0].innerText
                //弹幕发送的时间
                console.log("stime",stime)

                var self = this

                uni.sendSocketMessage({
                    data:JSON.stringify({"user_id": this.user_id,"live_id": this.live_id,"cmd": 1,"content":self.content ,"types": 1})
                })

                this.content = ""
            },//发送弹幕消息
            VideoDetail(id){
                uni.request({
                    url: 'http://127.0.0.1:8081/video/detail', //仅为示例,并非真实接口地址。
                    data: {
                        video_id: this.video_id
                    },
                    method:"POST",
                    header: {
                        'content-type': 'application/x-www-form-urlencoded'
                    },
                    success: (res) => {
                        console.log(res.data);
                        this.text = 'request success';
                    }
                });
            },//获取视频详情
            Get(){
                var player = new Aliplayer({
                    id: "player-con",
                    source: "https://lululucky.oss-cn-shanghai.aliyuncs.com/bc9eb9f0174df93666237690c6f503f3.mp4?OSSAccessKeyId=LTAI5tLNL5i8CpPZMXg34TaB&Expires=1717811349&Signature=mW03oYzuKeUZur5cRenknBkxJog%3D",
                    width: "100%",
                    height: "500px",
                    autoplay: true,
                    isLive: false,
                    controlBarVisibility: 'click',    /* The mode of the status bar, which is set to Click. */
                    showBarTime: '10000',             /* The time period that you must wait before the status bar is hidden, which is set to 10,000 milliseconds. */
                    components: [{
                      name: 'AliplayerDanmuComponent',
                      type: AliPlayerComponent.AliplayerDanmuComponent,
                      args: [this.danmukuList]
                    }]
                  }, function (player) {
                    console.log("The player is created");
                  });
            }
        }
    }
</script>

<style>
 @import url("https://g.alicdn.com/apsara-media-box/imp-web-player/2.20.3/skins/default/aliplayer-min.css");
</style>

#### 技术栈
列出Web端所使用的技术和框架。

**示例:**
  • 前端框架:React.js
  • 状态管理:Redux
  • 样式:CSS, SASS
  • 构建工具:Webpack
  • 其他库:Axios, React Router
     
安装与运行

提供安装和运行Web端项目的详细步骤。

示例:

1. 克隆仓库:
2. 安装依赖:
  • cd web-app
  • npm install
3. 运行开发服务器:
  • npm start
4. 打开浏览器访问:

http://localhost:3000
 

目录结构

列出项目的目录结构及各目录的功能介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值