准备工作:首选需要一个通过ICP备案的域名,如minglina.com。开通腾讯云云直播服务。
一、添加推流域名、播放域名
使用云直播服务,至少需要2个域名,一个作为推流域名,一个作为播放域名,且推流和播放不能使用相同的域名。但可以通过二级域名来进行区分,不局限于是否两个子域名,例如可以使用 push.minglina.com
作为推流域名,将 play.minglina.com
作为播放域名。
参考域名管理。
二、生成推流、播放地址
1.生成推流地址
云直播控制台提供地址生成器功能,支持通过填写地址拼接信息,辅助用户快速生成推流/播放地址。其中直播地址主要由域名(domain)、应用名称(AppName)、流名称(StreamName)以及鉴权 Key 组成。
域名管理,选择推流域名->推流配置,获取推流的鉴权key。
参考直播推流 。
2.生成播放地址
域名管理,选择播放域名->访问控制,获取播放的鉴权key。
参考 推流url 。
3.代码生成推流、播放地址:
import cn.hutool.core.date.DateUtil;
import com.ynfy.buss.live.entity.vo.LiveVO;
import com.ynfy.buss.live.service.ILiveService;
import com.ynfy.common.utils.LiveUtil;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @Description: 直播服务
* @Author: jeecg-boot
* @Date: 2025-06-11
* @Version: V1.0
*/
@Slf4j
@Service
public class LiveServiceImpl implements ILiveService {
/**
* 是否开启直播功能
*/
@Value("${tencent.cloud.live.enable}")
public Boolean liveEnable;
/**
* 推流域名
*/
@Value("${tencent.cloud.live.pushDomain}")
public String pushDomain;
/**
* 播放域名
*/
@Value("${tencent.cloud.live.playDomain}")
public String playDomain;
/**
* app名称
*/
@Value("${tencent.cloud.live.appName}")
public String appName;
/**
* 推流鉴权key
*/
@Value("${tencent.cloud.live.pushKey}")
public String pushKey;
/**
* 播放鉴权key
*/
@Value("${tencent.cloud.live.playKey}")
public String playKey;
/**
* 获取推流、播放 URL
*
* @param streamName
*/
@Override
public LiveVO getPushPlayUrl(String streamName, String endTime) {
if (!liveEnable) {
throw new JeecgBootException("直播功能暂未开放");
}
try {
//获取推流鉴权信息
String pushAuthInfo = LiveUtil.getSafeUrl(pushKey, streamName, DateUtil.parseDateTime(endTime).getTime() / 1000);
//获取播放鉴权信息
String playAuthInfo = LiveUtil.getSafeUrl(playKey, streamName, DateUtil.parseDateTime(endTime).getTime() / 1000);
//推流地址
String rtmpPushUrl = new StringBuilder().append("rtmp://").append(pushDomain).append("/")
.append(appName).append("/").append(streamName).append("?").append(pushAuthInfo).toString();
String webRTCPushUrl = new StringBuilder().append("webrtc://").append(pushDomain).append("/")
.append(appName).append("/").append(streamName).append("?").append(pushAuthInfo).toString();
//播放地址
String rtmpPlayUrl = new StringBuilder().append("rtmp://").append(playDomain).append("/")
.append(appName).append("/").append(streamName).append("?").append(playAuthInfo).toString();
String webRTCPlayUrl = new StringBuilder().append("webrtc://").append(playDomain).append("/")
.append(appName).append("/").append(streamName).append("?").append(playAuthInfo).toString();
String hlsPlayUrl = new StringBuilder().append("http://").append(playDomain).append("/")
.append(appName).append("/").append(streamName).append(".m3u8?").append(playAuthInfo).toString();
LiveVO live = new LiveVO();
live.setRtmpPushUrl(rtmpPushUrl);
live.setWebrtcPushUrl(webRTCPushUrl);
live.setRtmpPlayUrl(rtmpPlayUrl);
live.setWebrtcPlayUrl(webRTCPlayUrl);
live.setHlsPlayUrl(hlsPlayUrl);
return live;
} catch (Exception e) {
e.printStackTrace();
log.error("获取推流 URL出错:{}", e.getMessage());
throw new JeecgBootException("获取推流、播放 URL出错");
}
}
}
生成权限信息工具类:
import java.security.MessageDigest;
/**
* 腾讯云直播工具类
*/
public class LiveUtil {
public static void main(String[] args) {
System.out.println(getSafeUrl("txrtmp", "11212122", 1469762325L));
}
private static final char[] DIGITS_LOWER =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/*
* KEY+ streamName + txTime
*/
public static String getSafeUrl(String key, String streamName, long txTime) {
String input = new StringBuilder().
append(key).
append(streamName).
append(Long.toHexString(txTime).toUpperCase()).toString();
String txSecret = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
txSecret = byteArrayToHexString(
messageDigest.digest(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return txSecret == null ? "" :
new StringBuilder().
append("txSecret=").
append(txSecret).
append("&").
append("txTime=").
append(Long.toHexString(txTime).toUpperCase()).
toString();
}
private static String byteArrayToHexString(byte[] data) {
char[] out = new char[data.length << 1];
for (int i = 0, j = 0; i < data.length; i++) {
out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
out[j++] = DIGITS_LOWER[0x0F & data[i]];
}
return new String(out);
}
}
三、前端页面推流
1.首选需调用上述接口生成推流和播放地址。
2.推流
web推流
云直播提供了推流 SDK TXLivePusher
用于 Web 推流。 index.html页面的body 部分引入TXLivePusher js脚本。
需要获取摄像头使用权限。推流页面代码:
<template>
<div
id="local_video"
ref="local_video"
style="width: 100%; height: 500px; display: flex; align-items: center; justify-content: center;background-color: #bababa"
></div>
<div style="display: flex;justify-content: center">
<a-space style="margin-top: 30px;padding: 0 20px" size="large">
<a-button type="dashed" block preIcon="ant-design:video-camera-outlined" @click="startLive"
v-if="liveIng == false">开始直播
</a-button>
<a-button type="primary" danger preIcon="ant-design:minus-circle-filled" @click="stopLive"
v-else>停止直播
</a-button>
</a-space>
</div>
</template>
<script lang="ts" setup>
import {ref} from "vue";
import {useMessage} from "@/hooks/web/useMessage";
const record = ref<any>(); //传入你的数据
const local_video = ref();
const livePusher = ref<any>();
const {createMessage} = useMessage();
const liveIng = ref<boolean>(false);
onMounted(() => {
//初始化播放器
initLivePusher()
});
//初始化播放器
function initLivePusher() {
livePusher.value = new TXLivePusher();
livePusher.value.setRenderView('local_video');
livePusher.value.videoView.muted = true;
// // 设置视频质量
livePusher.value.setVideoQuality('720p');
// // 设置音频质量
livePusher.value.setAudioQuality('standard');
// // 自定义设置帧率
livePusher.value.setProperty('setVideoFPS', 25);
}
//开始直播
function startLive() {
// 采集完摄像头和麦克风之后自动推流
Promise.all([livePusher.value.startCamera(), livePusher.value.startMicrophone()])
.then(function () {
livePusher.value.startPush(record.value.webrtcPushUrl);
});
observerLive()
}
//停止直播
function stopLive() {
livePusher.value.stopPush();
// 关闭摄像头
livePusher.value.stopCamera();
// 关闭麦克风
livePusher.value.stopMicrophone();
liveIng.value = false
}
function observerLive() {
livePusher.value.setObserver({
onError: function (status, message) {
createMessage.error(message)
stopLive()
},
onPushStatusUpdate: function (status, message) {
if (status == 2) {
liveIng.value = true
createMessage.success("与服务器连接成功,直播推流开始啦!")
}
if (status == 0) {
createMessage.warn("与服务器连接断开,已关闭直播推流!")
}
},
});
}
</script>
<style lang="less" scoped>
</style>
参考 TXLivePusher 。
另一种是OBS工具推流。下载后添加源。
设置直播推流地址和推流码。
设置好后点击开始直播。
四、播放
1.web播放,使用腾讯云视立方SDK播放。
进入视立方控制台申请web端License:
安装 tcplayer 的 npm 包:
npm install tcplayer.js
licenseUrl为上述环节申请的。tcplayer播放器拉流WebRTC地址播放。
<template>
<div class="course-banner-inner">
<section class="section-study">
<div class="wrapper-player">
<div class="player-container">
<div class="loki-player-wrapper" id="loki-player-wrapper">
<div class="tc_player" id="course-playback-player"
style="transform: translate(0px, 0px);">
<video id="live-player" width="1200" height="618" preload="auto" playsinline
webkit-playsinline>
</video>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import TCPlayer from "tcplayer.js";
import "tcplayer.js/dist/tcplayer.min.css";
import { onBeforeUnmount, ref, watchEffect } from "vue";
import { useMessage } from "@/hooks/web/useMessage";
const { createMessage, } = useMessage();
const livePlayer = ref<any>();
const props = defineProps({
form: {
type: Object,
default: {}
},
});
watchEffect(() => {
if (props.form?.isLive) { //直播课程创建直播播放器
createLivePlayer(props.form.webrtcPlayUrl);
}
});
//创建直播播放器
function createLivePlayer(playUrl) {
// live-player 为播放器容器 ID
livePlayer.value = TCPlayer("live-player", {
sources: [{
src: playUrl // 播放地址
}],
licenseUrl: "https://license.vod2.myqcloud.com/license/v4/13013228971_1/v_cube.license" // license 地址,必传
});
livePlayer.value.on("error", function(e) {
createMessage.error("直播出错啦:" + (e.data.source.message ?? "") + "(代码:" + e.data.code + ")");
});
livePlayer.value.on("play", function() {
createTimer();
});
}
//页面销毁前解除监听
onBeforeUnmount(async () => {
//销毁播放器
if (livePlayer.value) {
livePlayer.value.dispose();
}
});
</script>
<style scoped>
</style>
参考 Tcplayer。
2.uniapp播放。
一种方式是<live-player> 标签。<live-player> 是小程序内部用于支持音视频下行(播放)能力的功能标签。开通该标签需要满足规定的类目,详见live-player。
另一种是小程序端和app端都使用video标签播放。在前面获取到的播放地址中已经生成了HLS播放地址。
<template>
<view class="player-content">
<video id="live-player" ref="livePlayer" :src="playUrl" autoplay controls class="live-player" @error="handleError">
</video>
</view>
</template>
<script>
export default {
props: ['form'],
data() {
return {
playUrl: ""
};
},
watch: {
form: {
handler(val, oldVal) {
if (val?.isLive) { //直播课程创建直播播放器
if (val?.hlsPlayUrl) {
//替换直播播放地址,从服务器代理获取推流
this.playUrl = val.hlsPlayUrl.replace("http://play.minglin.com",
"https://exam.minglin.com/hls-player");
}
}
},
immediate: true
},
},
methods: {
handleError() {
uni.showToast({
icon: "none",
title: "获取直播推流失败"
})
},
}
}
</script>
<style lang="scss" scoped>
.player-content {
position: relative;
width: 750rpx;
height: 450rpx;
display: flex;
background-size: 100% 100%;
.live-player {
width: 100%;
height: 100%;
position: relative;
}
}
</style>
需要注意的是生成的HLS播放地址是http的,小程序支持https。所以需要转换下,然后通过nginx代理播放直播流。
五、效果
1.web端:
2.uniapp端: