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. 克隆仓库:
- git clone https://github.com/your-repo/web-app.git
2. 安装依赖:
- cd web-app
- npm install
3. 运行开发服务器:
- npm start
4. 打开浏览器访问:
目录结构
列出项目的目录结构及各目录的功能介绍