Appearance
视频直播
阿里云提供了完善的直播服务,本章来为我们的网站添加直播功能。
直播配置需要在 阿里云控制台 进行设置。直播数据分为推流(比如使用 OBS 直播)和播流(网页或 APP 中展示直播视频)。
域名配置
直播的推流和播流都需要设置独立的域名,首先在控制台选择域名管理来添加域名。
推流域名
首先添加推流域名
播流域名
下面来设置播放直播视频的播流域名设置
CNAME
在直播控制台查看推流与播流域名需要设置的域名解析的 CNAME 值
下面进入域名管理来设置域名的 CNAME 记录,使域名可以使用直播服务
点击域名进行「域名管理」界面,然后点击「域名解析」进入域名解析界面,开始设置 CNAME 解析。
下面设置推流域名 CNAME
下面设置播流域名 CNAME
最终两个域名的设置如下
经过几分钟后在直播控制台可以看到域名「正常运行」的提示
URL 鉴权
我们后台要通过程序开通直播,需要设置域名的 URL 鉴权。访问直播控制台域名管理中的域名配置
首先设置推流域名的访问控制
接着设置播流域名的访问设置
关联域名
我们需要将播流域名与推流域名进行关联,点击播流域名的域名配置。
CORS
因为是跨域请求直播数据,所以需要对跨域进行处理。
SSL 证书
下面来配置播流域名的 SSL 证书,在后盾人文档库查看 [SSL 证书](https://doc.houdunren.com/linux/10 SSL 证书.html) 的使用,并获取证书的PEM。
下面到阿里云播流域名处设置 HTTPS 证书
直播测试
下面安装OBS 与 VLC 软件来开始直播,主要是测试我们的直播配置是否正确。
如果下载慢可以通过腾讯管理或 360 管理中的应用商店安装
生成地址
进行直播控制台的地址生成器来生成推流和播流地址
开始直播
下面是 OBS 中的直播配置
- 服务器使用生成的推流地址中截止到 AppName 字符
- 串流密钥 从生成的推流地址中 StreamName 到结速
然后点击开始直播,可以通过 OBS 状态栏看到直播状态。
观看直播
复制上面生成地址步骤中生成的M3U8播流地址。然后打开 VLC 软件中的 文件 > 打开网络 ,将播流地址粘贴进去。
点击打开后,我们将可以看到直播画面。这也表示我们直播配置是正确的。
LARAVEL
下面我来使用 PHP 后台程序来控制直播,当直播开始时,网站会接到直播通知,来控制直播间视频的播放。
首先安装扩展名 aliyun/openapi-sdk-php
composer require alibabacloud/sdk
数据库
下面是直播使用到的迁移文件
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLivesTable extends Migration
{
public function up()
{
Schema::create('lives', function (Blueprint $table) {
$table->id();
$table->string('config', 500)->comment('直播数据');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('lives');
}
}
模型
Live 模型文件用于保存直播配置数据
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* 直播
* @package App\Models
*/
class Live extends Model
{
use HasFactory;
protected $fillable = ['config'];
protected $casts = ['config' => 'array'];
}
直播服务
下面是在 Laravel 项目中定义的直播服务类
- 生成推流与播流地址
- 向阿里云绑定通知
<?php
namespace Services;
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
/**
* 直播服务
* @package Services
*/
class LiveService
{
/**
* 生成推流地址
* @param mixed $domain 推流域名如 live.houdunren.com
* @param mixed $app 自定义应用名称
* @param mixed $stream 自定义流名称
* @param mixed|null $key URL鉴权中的主KEY,访问直播控制台中 域名管理>推流域名配置>访问控制 获取
* @param int $expire 有效时间
* @return string|string[]
*/
public function pushUrl($domain, $app, $stream, $key = null, $expire = 1200)
{
//没有鉴权Key时
if (is_null($key)) {
return 'rtmp://' . $domain . '/' . $app . '/' . $stream;
}
$timeout = time() + $expire;
$sstring = '/' . $app . '/' . $stream . '-' . $timeout . '-0-0-' . $key;
$url = 'rtmp://' . $domain . '/' . $app . '/' . $stream . '?auth_key=' . $timeout . '-0-0-' . md5($sstring);
$data = explode($stream, $url);
return [
'url' => trim($data[0], '/'),
'key' => $stream . $data[1],
];
}
/**
* 生成播流地址
* @param mixed $domain 播流域名如 play.houdunren.com
* @param mixed $app 自定义应用名称
* @param mixed $stream 自定义流名称
* @param mixed|null $key URL鉴权中的主KEY,访问直播控制台中 域名管理>播流域名配置>访问控制 获取
* @param int $expire 有效时间
* @return string|string[]
*/
public function playUrl($domain, $app, $stream, $key = null, $expire = 1200)
{
//没有鉴权Key时
if (is_null($key)) {
$rtmp_play_url = 'rtmp://' . $domain . '/' . $app . '/' . $stream;
$flv_play_url = 'https://' . $domain . '/' . $app . '/' . $stream . '.flv';
$hls_play_url = 'https://' . $domain . '/' . $app . '/' . $stream . '.m3u8';
} else {
$timeStamp = time() + $expire;
$rtmp_sstring = '/' . $app . '/' . $stream . '-' . $timeStamp . '-0-0-' . $key;
$rtmp_md5hash = md5($rtmp_sstring);
$rtmp_play_url = 'rtmp://' . $domain . '/' . $app . '/' . $stream . '?auth_key=' . $timeStamp . '-0-0-' . $rtmp_md5hash;
$flv_sstring = '/' . $app . '/' . $stream . '.flv-' . $timeStamp . '-0-0-' . $key;
$flv_md5hash = md5($flv_sstring);
$flv_play_url = 'https://' . $domain . '/' . $app . '/' . $stream . '.flv?auth_key=' . $timeStamp . '-0-0-' . $flv_md5hash;
$hls_sstring = '/' . $app . '/' . $stream . '.m3u8-' . $timeStamp . '-0-0-' . $key;
$hls_md5hash = md5($hls_sstring);
$hls_play_url = 'https://' . $domain . '/' . $app . '/' . $stream . '.m3u8?auth_key=' . $timeStamp . '-0-0-' . $hls_md5hash;
}
return [
'rtmp' => $rtmp_play_url,
'flv' => $flv_play_url,
'hls' => $hls_play_url,
];
}
/**
* 直播关闭时阿里云向网站通知地址设置
* accessKeyId 与 accessKeySecret 请通过阿里云帐号获取
* @param mixed $domain 推流域名如 live.houdunren.com
* @param string $url 阿里云通知地址(如OBS断流发生时)
* @return array|void
* @throws ClientException
*/
public function notify($accessKeyId, $accessKeySecret, $domain, $url)
{
AlibabaCloud::accessKeyClient($accessKeyId, $accessKeySecret)
->regionId('cn-hangzhou')
->asDefaultClient();
$result = AlibabaCloud::rpc()
->product('live')
// ->scheme('https') // https | http
->version('2016-11-01')
->action('SetLiveStreamsNotifyUrlConfig')
->method('POST')
->host('live.aliyuncs.com')
->options([
'query' => [
'RegionId' => 'cn-hangzhou',
'NotifyUrl' => $url,
'DomainName' => $domain,
],
])
->request();
return $result->toArray();
}
}
控制器
控制器 LiveController 实现生成直播数据与接收阿里云通知方法
<?php
namespace App\Http\Controllers;
use AlibabaCloud\Client\Exception\ClientException;
use App\Models\Live;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Request;
use Services\LiveService;
use Str;
use Log;
/**
* 直播
* @package App\Http\Controllers
*/
class LiveController extends Controller
{
/**
* 获取直播数据
* 创建直播后会在表中添加播放与推流地址及直播状态数据
* @return mixed
*/
public function get()
{
$live = Live::first();
return $live ? $live->value('config') : [
"play_url" => [],
"push_url" => [],
"is_live" => false
];
}
/**
* 生成直播数据
* @param LiveService $liveService
* @return array
* @throws Exception
* @throws BindingResolutionException
* @throws ClientException
*/
public function make(LiveService $liveService)
{
$liveName = Str::random(8);
$pushUrl = $liveService->pushUrl(config('hd.live.push_domain'), 'houdunren', $liveName, config('hd.live.push_key'));
$playUrl = $liveService->playUrl(config('hd.live.play_domain'), 'houdunren', $liveName, config('hd.live.play_key'));
$info = $liveService->notify(
config('hd.aliyun.accessKeyId'),
config('hd.aliyun.accessKeySecret'),
config('hd.live.push_domain'),
route('live.notify')
);
Log::info($info);
$live = Live::firstOrNew(['id' => 1]);
$live->config = ['play_url' => $playUrl, 'push_url' => $pushUrl, 'is_live' => false];
$live->save();
return $this->success('推流成功', $live->config);
}
/**
* 阿里云通知处理
* obs 推流后接收阿里云发送来的消息
* @param Request $request
* @return void
*/
public function notify(Request $request)
{
$live = Live::find(1);
switch ($request->action) {
case 'publish_done':
$live->config = ['is_live' => false] + $live->config;
break;
case 'publish':
$live->config = ['is_live' => true] + $live->config;
break;
}
Log::info($live->config);
$live->save();
}
}
路由定义
route/web.php
//接收阿里云直播变更通知
Route::any('live/notify', [LiveController::class, 'notify'])->name('live.notify');
route/api.php
//创建直播
Route::get('live/make', [LiveController::class, 'make']);
//获取直播数据
Route::get('live', [LiveController::class, 'get']);
前端组件
下面来创建 vue 直播组件。使用 hls.js 播放 m3u8 直播数据
<template>
<div>
<video class="w-full border shadow -mt-5" id="live" controls v-show="config.is_live"></video>
<div class="mt-3" v-if="helper.user().isAdmin">
<table class="table table-vcenter bg-white table-bordered" v-if="config.push_url">
<tbody>
<tr>
<td>直播地址</td>
<td>
服务器: {{ config.push_url.url }} <br />
串流密钥: {{ config.push_url.key }}
</td>
</tr>
<tr>
<td>播放地址</td>
<td>{{ config.play_url.hls }}</td>
</tr>
</tbody>
</table>
<div class="mt-3 border py-3 px-5 bg-white flex">
<a href="#" class="btn btn-success text-white" @click="makeLive">创建直播</a>
<div class="bg-white text-gray-500 text-sm ml-2 flex-1 flex items-center">
创建直播指获取推流与播流数据,需要使用推流数据在 OBS 开启直播后才是真正的开启直播
</div>
</div>
</div>
</div>
</template>
<script setup>
import liveApi from 'api/liveApi'
import { ref } from 'vue'
import helper from 'utils/helper'
//直播数据
const config = ref(await liveApi.get())
//显示播放器
if (config.value.is_live) {
const videoSrc = config.value.play_url.hls
setTimeout(() => {
let video = document.getElementById('live')
if (Hls.isSupported()) {
let hls = new Hls()
hls.loadSource(videoSrc)
hls.attachMedia(video)
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play()
})
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc
video.addEventListener('loadedmetadata', function () {
video.play()
})
}
})
}
//创建直播
const makeLive = async () => {
const { data } = await liveApi.make()
config.value = data
}
</script>
<style>
</style>
用于请求接口的 liveApi
import axios from 'plugins/axios'
export default {
async get() {
return await axios.get(`live`)
},
async make() {
return await axios.get(`live/make`)
}
}