Skip to content

视频直播

向军大叔每晚八点在 抖音bilibli 直播

xj-small

阿里云提供了完善的直播服务,本章来为我们的网站添加直播功能。

直播配置需要在 阿里云控制台 进行设置。直播数据分为推流(比如使用 OBS 直播)和播流(网页或 APP 中展示直播视频)。

image-20200729150441202

域名配置

直播的推流和播流都需要设置独立的域名,首先在控制台选择域名管理来添加域名。

推流域名

首先添加推流域名

image-20200729150632892

播流域名

下面来设置播放直播视频的播流域名设置

image-20200729150939146

CNAME

在直播控制台查看推流与播流域名需要设置的域名解析的 CNAME 值

image-20200729151501866

下面进入域名管理来设置域名的 CNAME 记录,使域名可以使用直播服务

image-20200729151157858

点击域名进行「域名管理」界面,然后点击「域名解析」进入域名解析界面,开始设置 CNAME 解析。

下面设置推流域名 CNAME

image-20200729152159443

下面设置播流域名 CNAME

image-20200729151723972

最终两个域名的设置如下

image-20200729152032798

经过几分钟后在直播控制台可以看到域名「正常运行」的提示

image-20200729152409140

URL 鉴权

我们后台要通过程序开通直播,需要设置域名的 URL 鉴权。访问直播控制台域名管理中的域名配置

image-20200729152638999

首先设置推流域名的访问控制

image-20200729152750334

接着设置播流域名的访问设置

image-20200729152859867

关联域名

我们需要将播流域名与推流域名进行关联,点击播流域名的域名配置。

image-20200729153152559

CORS

因为是跨域请求直播数据,所以需要对跨域进行处理。

image-20210811213217299

SSL 证书

下面来配置播流域名的 SSL 证书,在后盾人文档库查看 [SSL 证书](https://doc.houdunren.com/linux/10 SSL 证书.html) 的使用,并获取证书的PEM

image-20210811194208074

下面到阿里云播流域名处设置 HTTPS 证书

image-20210811194624929

直播测试

下面安装OBSVLC 软件来开始直播,主要是测试我们的直播配置是否正确。

如果下载慢可以通过腾讯管理或 360 管理中的应用商店安装

生成地址

进行直播控制台的地址生成器来生成推流和播流地址

image-20210811195550293

开始直播

下面是 OBS 中的直播配置

  • 服务器使用生成的推流地址中截止到 AppName 字符
  • 串流密钥 从生成的推流地址中 StreamName 到结速

image-20210811200921969

然后点击开始直播,可以通过 OBS 状态栏看到直播状态。

image-20200729154630701

观看直播

复制上面生成地址步骤中生成的M3U8播流地址。然后打开 VLC 软件中的 文件 > 打开网络 ,将播流地址粘贴进去。

image-20210811201032371

点击打开后,我们将可以看到直播画面。这也表示我们直播配置是正确的。

image-20210811201328202

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`)
  }
}