Hướng dẫn generate token agora video call với laravel

  • June 30, 2020
  • 1868

Hôm nay mình sẽ hướng dẫn các bạn generate token agora phục vụ cho video call

Xây dựng thư viện Agora

Tạo lớp AccessToken

Tạo file app/Libs/Agora/AccessToken.php


<?php

namespace App\Libs\Agora;

use App\Libs\Agora\Message;

class AccessToken
{
    public static $Privileges = array(
        "kJoinChannel" => 1,
        "kPublishAudioStream" => 2,
        "kPublishVideoStream" => 3,
        "kPublishDataStream" => 4,
        "kPublishAudioCdn" => 5,
        "kPublishVideoCdn" => 6,
        "kRequestPublishAudioStream" => 7,
        "kRequestPublishVideoStream" => 8,
        "kRequestPublishDataStream" => 9,
        "kInvitePublishAudioStream" => 10,
        "kInvitePublishVideoStream" => 11,
        "kInvitePublishDataStream" => 12,
        "kAdministrateChannel" => 101,
        "kRtmLogin" => 1000,
    );

    public $appID;
    public $appCertificate;
    public $channelName;
    public $uid;
    public $message;

    public function __construct()
    {
        $this->message = new Message();
    }

    public function setUid($uid)
    {
        if ($uid === 0) {
            $this->uid = "";
        } else {
            $this->uid = $uid . '';
        }
    }

    public function isNonemptyString($name, $str)
    {
        if (is_string($str) && $str !== "") {
            return true;
        }
        echo $name . " check failed, should be a non-empty string";

        return false;
    }

    public static function init($appID, $appCertificate, $channelName, $uid)
    {
        $accessToken = new AccessToken();

        if (!$accessToken->isNonemptyString("appID", $appID) ||
            !$accessToken->isNonemptyString("appCertificate", $appCertificate) ||
            !$accessToken->isNonemptyString("channelName", $channelName)) {
            return null;
        }

        $accessToken->appID = $appID;
        $accessToken->appCertificate = $appCertificate;
        $accessToken->channelName = $channelName;

        $accessToken->setUid($uid);
        $accessToken->message = new Message();

        return $accessToken;
    }

    public static function initWithToken($token, $appCertificate, $channel, $uid)
    {
        $accessToken = new AccessToken();
        if (!$accessToken->extract($token, $appCertificate, $channel, $uid)) {
            return null;
        }

        return $accessToken;
    }

    public function addPrivilege($key, $expireTimestamp)
    {
        $this->message->privileges[$key] = $expireTimestamp;

        return $this;
    }

    public function extract($token, $appCertificate, $channelName, $uid)
    {
        $ver_len = 3;
        $appid_len = 32;
        $version = substr($token, 0, $ver_len);
        if ($version !== "006") {
            echo 'invalid version ' . $version;
            return false;
        }

        if (!$this->isNonemptyString("token", $token) ||
            !$this->isNonemptyString("appCertificate", $appCertificate) ||
            !$this->isNonemptyString("channelName", $channelName)) {
            return false;
        }

        $appid = substr($token, $ver_len, $appid_len);
        $content = (base64_decode(substr($token, $ver_len + $appid_len, strlen($token) - ($ver_len + $appid_len))));

        $pos = 0;
        $strPost = "0";
        $len = unpack("v", $content . substr($strPost, 2))[1];
        $pos += 2;
        $sig = substr($content, $pos, $len);
        $pos += $len;
        $crc_channel = unpack("V", substr($content, $pos, 4))[1];
        $pos += 4;
        $crc_uid = unpack("V", substr($content, $pos, 4))[1];
        $pos += 4;
        $msgLen = unpack("v", substr($content, $pos, 2))[1];
        $pos += 2;
        $msg = substr($content, $pos, $msgLen);

        $this->appID = $appid;
        $message = new Message();
        $message->unpackContent($msg);
        $this->message = $message;

        //non reversable values
        $this->appCertificate = $appCertificate;
        $this->channelName = $channelName;
        $this->setUid($uid);

        return true;
    }

    public function build()
    {
        $msg = $this->message->packContent();
        $val = array_merge(
            unpack("C*", $this->appID),
            unpack("C*", $this->channelName),
            unpack("C*", $this->uid),
            $msg
        );

        $sig = hash_hmac('sha256', implode(array_map("chr", $val)), $this->appCertificate, true);

        $crc_channel_name = crc32($this->channelName) & 0xffffffff;
        $crc_uid = crc32($this->uid) & 0xffffffff;

        $content = array_merge(
            unpack("C*", $this->packString($sig)),
            unpack("C*", pack("V", $crc_channel_name)),
            unpack("C*", pack("V", $crc_uid)),
            unpack("C*", pack("v", count($msg))),
            $msg
        );
        $version = "006";
        $ret = $version . $this->appID . base64_encode(implode(array_map("chr", $content)));

        return $ret;
    }

    public function packString($value)
    {
        return pack("v", strlen($value)) . $value;
    }
}

Tạo lớp Message

Tạo file app/Libs/Agora/Message.php


<?php

namespace App\Libs\Agora;

class Message
{
    public $salt;
    public $ts;
    public $privileges;
    public function __construct()
    {
        $this->salt = rand(0, 100000);
        $date = new \DateTime("now", new \DateTimeZone('UTC'));
        $this->ts = $date->getTimestamp() + 24 * 3600;
        $this->privileges = array();
    }

    public function packContent()
    {
        $buffer = unpack("C*", pack("V", $this->salt));
        $buffer = array_merge($buffer, unpack("C*", pack("V", $this->ts)));
        $buffer = array_merge($buffer, unpack("C*", pack("v", count($this->privileges))));
        foreach ($this->privileges as $key => $value) {
            $buffer = array_merge($buffer, unpack("C*", pack("v", $key)));
            $buffer = array_merge($buffer, unpack("C*", pack("V", $value)));
        }
        return $buffer;
    }

    public function unpackContent($msg)
    {
        $pos = 0;
        $salt = unpack("V", substr($msg, $pos, 4))[1];
        $pos += 4;
        $ts = unpack("V", substr($msg, $pos, 4))[1];
        $pos += 4;
        $size = unpack("v", substr($msg, $pos, 2))[1];
        $pos += 2;

        $privileges = array();
        for ($i = 0; $i < $size; $i++) {
            $key = unpack("v", substr($msg, $pos, 2));
            $pos += 2;
            $value = unpack("V", substr($msg, $pos, 4));
            $pos += 4;
            $privileges[$key[1]] = $value[1];
        }
        $this->salt = $salt;
        $this->ts = $ts;
        $this->privileges = $privileges;
    }
}

Tạo lớp RtmTokenBuilder

Tạo file app/Libs/Agora/RtmTokenBuilder.php


<?php

namespace App\Libs\Agora;

use App\Libs\Agora\AccessToken;

class RtmTokenBuilder
{
    const ROLE_RTM_USER = 1;
    # appID: The App ID issued to you by Agora. Apply for a new App ID from
    # Agora Dashboard if it is missing from your kit. See Get an App ID.
    # appCertificate: Certificate of the application that you registered in
    #                  the Agora Dashboard. See Get an App Certificate.
    # channelName:Unique channel name for the AgoraRTC session in the string format
    # userAccount: The user account.
    # role: Role_Rtm_User = 1
    # privilegeExpireTs: represented by the number of seconds elapsed since
    #                    1/1/1970. If, for example, you want to access the
    #                    Agora Service within 10 minutes after the token is
    #                    generated, set expireTimestamp as the current
    #                    timestamp + 600 (seconds)./
    public static function buildToken($appID, $appCertificate, $userAccount, $role, $privilegeExpireTs)
    {
        $token = AccessToken::init($appID, $appCertificate, $userAccount, '');
        $Privileges = AccessToken::$Privileges;
        $token->addPrivilege($Privileges["kRtmLogin"], $privilegeExpireTs);
        
        return $token->build();
    }
}

Tạo agora service

Tạo file app/Services/AgoraService.php


<?php

namespace App\Services;

use App\Libs\Agora\AccessToken;
use App\Libs\Agora\RtmTokenBuilder;
use Log;

class AgoraService
{
    private $appID;
    private $appCertificate;

    public function __construct()
    {
        $this->appID = config('agora.app_id');
        $this->appCertificate = config('agora.app_certificate');
    }

    public function getRtcToken(string $channelName, int $uid = 0, int $expireTimestamp = 0)
    {
        try {
            $builder = AccessToken::init($this->appID, $this->appCertificate, $channelName, $uid);
            $builder->addPrivilege(AccessToken::$Privileges["kJoinChannel"], $expireTimestamp);
            
            return $builder->build();
        } catch (\Exception $e) {
            Log::error('[AGORA_GENERATE_RTC_TOKEN_ERROR] '. $e->getMessage());

            return false;
        }
    }

    public function getRtmToken(string $channelName, int $expireTimestamp = 0)
    {
        try {
            $token = RtmTokenBuilder::buildToken(
                $this->appID,
                $this->appCertificate,
                $channelName,
                RtmTokenBuilder::ROLE_RTM_USER,
                $expireTimestamp
            );

            return $token;
        } catch (\Exception $e) {
            Log::error('[AGORA_GENERATE_RTM_TOKEN_ERROR] '. $e->getMessage());

            return false;
        }
    }
}

Tạo file config agora

Tạo file app/config/agora.php


<?php

return [
    'app_id'               => env('AGORA_APP_ID', null),
    'app_certificate'      => env('AGORA_APP_CERTIFICATE', null),
];

Generate token agora

Tạo controller app/Http/Controller/AgoraController.php generate token


<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Services\AgoraService;
use Webpatser\Uuid\Uuid;
use Exception;

class AgoraController extends Controller
{
    /**
     * @var AgoraService
     */
    protected $agoraService;

    public function __construct()
    {
        $this->agoraService = app(AgoraService::class);
    }

    public function generateToken()
    {
        try {
            $channelName = (string) Uuid::generate(4);
            // Rtc token dùng để video call
            $token = $this->agoraService->getRtcToken($channelName);
            // Rtm token dùng để chat
            $rtmToken = $this->agoraService->getRtmToken($channelName);
            if (!$token || !$rtmToken) {
                $this->error('Generate token error');
            }

            $data = [
                'channel_name' => $channelName,
                'token' => $token,
                'rtm_token' => $rtmToken,
            ];

            return $this->success($data);
        } catch (Exception $e) {
            return $this->error($e->getMessage());
        }
    }
}

Tài liệu tham khảo agora generate token