加密視頻
在以后的開發項目中,很可能有做在線視頻的,而在線視頻就有個問題,因為在線播放,就很有可能視頻數據被抓包,如果這個在線視頻平台有付費視頻的話,這樣就會有人做點倒賣視頻的生意了,針對這個問題,目前國內有很多不錯的加密視頻平台,可以把你平台的視頻放在他們那里,然后通過他們的機制進行加密,然后做一套機制,當用戶使用平台播放時,其實是平台去加密視頻方請求過來的加密視頻,這樣就可以保證視頻的安全性了
常用加密視頻平台
鄙人聽說的,有保利威,金盾,還有很多很多我叫不上名,搜索引擎一搜就有一大堆的,本次教程說的是保利威
保利威
官網:傳送門 有直播和點播的服務,直播是什么不用多說了吧,點播的意思就是我上面說的,把錄制好的視頻放到平台那種
本教程只介紹使用雲點播功能
文檔手冊
點擊使用手冊的雲點播:使用手冊文檔:傳送門
選到h5視頻播放:當然你也可以使用flash,但是現在的平台基本都用的是h5了,所以本次選用h5的
官方文檔案例演示
復制這段代碼放到一個html文檔里,直接點那個瀏覽器標識打開測試:
這個頁面整個都是保利威給我我們提供的,可以加速播放,可以查看視頻參數,可以看列表,然后還有個【test】這個就是跑馬燈,相信有過購買視頻的朋友都知道,你看你的付費視頻時,都有自己的用戶名啥的,假如你自己私自錄制,傳出去,就是用這個跑馬燈就可以追蹤到你的,他官方給的實例就是這樣
但其實,因為我電腦裝了IDM工具,且它這個視頻只是展示,還沒有真正的加密(看到后面的你就知道為什么沒有真正的加密了),所以我的IDM自動嗅探視頻地址
且還支持持斷點播放
但是這個由於是案例,所以利用IDM直接就可以下載,且播放時還沒有水印:
好的,演示的視頻就是這樣了。
代碼實現
前期准備:
你需要注冊一個保利威的賬號,然后拿到id和secret,點設置 - API接口:
注意id和secretkey以及token,這里的token有讀的和寫的,后期會用到
上傳視頻
上傳一個測試的視頻,上傳的時候設置加密參數
上傳之前開啟加密設置
把加密設置打開,然后下面的移動端加密按你自己的需求來,我這里暫且不設置
相關的更多的視頻配置參數:傳送門
好的,我這里開始上傳,我上傳了一首歌的MV:
上傳完之后平台自動解碼,然后就會有一個視頻加密的id了:
轉碼之后,審核:
在審核的時候你就可以做一些相關的視頻設置:
這里還有很多的設置,不一一展示,自己體驗了
等待大概一兩分鍾之后,顯示已發布,表示可以用了:
其他參數配置
這里還有一些設置,比如播放域名設置,這些就自己去體驗了,我這里這些都直接用默認的
查看官方文檔:
看文檔得知有兩個步驟:
- 服務端獲取token,將token給客戶端
- 客戶端拿到token,開始播放視頻
獲取token
他官方文檔給的是php的,不存在,后面重構成Python的就行了
播放:
播放有兩種播放形式,一種是直接在playsafe里傳一個token,一種是給一個函數,函數必須帶視頻id和next,next是一個函數,在獲取token之后將token傳到next里即可,而這官方給的文檔是next(playsafe),這里有個坑,不是傳playsafe,而是傳token,我在這卡了很久
再次強調:視頻如果是加密的,需要設置加密參數 playsafe,playsafe有兩種形式,一個是傳token,一個是傳函數+next回調函數,且函數必須把token作為值傳進去
下面還有更多的配置,切換視頻之類的,不一一展示了,自己研究
代碼實現:
創建一個djangorestframework的項目,項目名為EncryptVideo,app名為app:
url:
polyv對象,在項目根目錄創建utils文件夾,該文件夾下創建polyv文件,定義一個Polyv對象
利用了設計模式里的單例模式返回一個polyv_video對象
view:request.META.get('REMOTE_ADDR') 可以獲取客戶端的IP地址 導入那個polyv對象
html,注意返回數據的層級,這個得根據你返回的數據來定
在這之前,我建議最好寫一個解決跨域請求的中間件,也放在utils目錄下:
啟動項目:Python manage.py runserver localhost:8000
那個html文件利用pycharm的功能從瀏覽器打開:
展示結果,朋友們,如果你遇到了這些坑,可以按我的方法試試
第一個坑
(為了不浪費大家時間,我上面給的截圖其實已經是我修改過並且正確的了)
發現返回的結果,sign params invild,意思就是說sign參數無效,那么再看官方文檔:
sign的計算規則根本由這個concated生成的:
但是這句話,有歧義,按照ASCII升序拼接,到底是先拼接了之后再按ASCII升序還是先按ASCII升序之后再拼接,而且他這句,按照ASCKII升序 key + value + key + value ... +value 拼接,確實不知道到底怎么拼接,所以我是這樣的:
當然你也可以用列表生成式,反正怎么舒服怎么來,反正拼接順序就是先按key排序之后再key+value組合
第二個坑,這個坑我個人認為操作的問題,其實不算坑,如果你遇到跟我一樣的問題,那恭喜你 嘻嘻
(上面的截圖也是正確的了,不浪費大家時間)
可以顯示,但是點擊播放放不了,打開控制台,報錯了:
(ip地址可以忽略,這是我之前測試的時候遇到的問題 )
這個問題,我跟你說,看似是同源策略的問題,其實並不是,就是我前面標注的那里:
就是因為兩個url沒有統一導致的,所以必須要統一,要嘛都在結束符【/】,要嘛都不帶
第三個坑,錯誤的以為保利威方給你報同源策略錯誤
(上面截圖也是已經是正確的了)
展示結果還是不能播放,並且連視頻縮略圖都沒了,而且如圖:
這個問題我是耗在這耗時最久的,報錯的意思就是跨域請求了,瀏覽器同源策略的問題,但是我把本地的啟動ip改成了我局域網的ip【192.168.0.8:8000】,html部分axios異步請求那里的也是【192.168.0.8:8000】,然后在django配置文件的這里,我添加了這個
中間價對response的設置前面也設置了,啟動還是不行,后面突然醒悟過來,打印看token是否有拿到,確實有拿到
而且在我們這個平台,保利威,客戶三者之間的關系,其實是這樣的:
也就是說,這個問題就是因為第2步之后的第3步上卡住,產生了跨域請求,所以這跟我們沒多大關系了,是保利威視頻那邊的問題。前面這句話前半句是對的(“第2步之后的第3步上卡住了”),后面的分析都是錯的,但是當時的我不知道啊,按着錯誤的思路,我想了想,我在保利威后台設置了一個視頻域名白名單:localhost:8000
有朋友回問,保利威那邊默認不就是對任何域名都沒有限制的嗎?是啊,但是我還是設置了,設置之后,果然還是不行,我還溯源,准備從這個next參數的開始:
我還去分析了他們的那個js文件,想找找next到底是什么:
發現簡直無從下手的。最后我就真的以為是保利威那邊的問題域名問題,就是要設置那個域名才行,但是我這里改下,那里改下還成功了:
可以播放了,可以調播放速度啥的,注意,默認打開沒有聲,是因為默認音量按鈕沒開,自己點那個喇叭圖標打開音量
那按着這個錯誤的思路,有朋友會說,我設置域名是127.0.0.1:8000,把項目的啟動ip也啟動為本地地址看看:
訪問:
所以還是不能直接是ip地址。
但是!但是,根據我的經驗,我還是不太放心,我又新建了一個django項目:
邏輯一樣,然后啟動的就是127.0.0.1:8001,然后保利威視頻域名限制我也刪了:
發現照樣能播放:
最后經我的研究發現,還是獲取token那里有問題,我把代碼重新寫的非常淺顯明了,什么列表生成式的寫法都棄了,就為了讀代碼順暢(上面的代碼截圖給的已經是正確的了)
結果這樣確實可以正常獲取token,然后next函數傳入token就直接播放了。
所以我錯誤的以為保利威那邊的問題,饒了很久才發現。
以上是我個人的從分析問題,走錯分析的路掉進坑了,然后得到的總結,如果你們沒有遇到,一氣呵成,那么你很棒,反正我是遇到了這些坑,最后不放心又測試了一次才找到根本問題的。當然以上都是我個人推斷,不代表絕對正確
好的,怎么代表我們真的配置好了呢?再上傳一個視頻,然后播放看看,如果真的沒問題,那以后就沒問題了,我開始上傳,並修改html上的id
瀏覽器打開:
相關代碼:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>保利威視頻測試</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script> </head> <body> <div id="player"></div> <script src="//player.polyv.net/script/player.js"></script> <script> var player = polyvPlayer({ wrap: '#player', width: 800, height: 533, vid: '2f57a436189b03930638e752c9a3761e_2', // 視頻id,對應賬號里的視頻id playsafe: function (vid, next) { axios.request({ url: "http://localhost:8000/polyv", method: "POST", data: { vid: vid } }).then(function (data) { console.log(data.data); next(data.data.token); }) } }); </script> </body> </html>

from django.contrib import admin from django.urls import path from app.views import Polyv urlpatterns = [ path('admin/', admin.site.urls), path('polyv', Polyv.as_view()), # 這里要與客戶端url對應,最后有沒有【/】要統一 ]

from django.conf import settings import time import requests import json import hashlib class PolyvPlayer(object): userId = settings.POLYV_CONFIG['userId'] secretkey = settings.POLYV_CONFIG['secretkey'] def tomd5(self, value): """取md5值""" return hashlib.md5(value.encode()).hexdigest() # 獲取視頻數據的token def get_video_token(self, videoId, viewerIp, viewerId=None, viewerName='', extraParams='HTML5'): """ :param videoId: 視頻id :param viewerId: 看視頻用戶id :param viewerIp: 看視頻用戶ip :param viewerName: 看視頻用戶昵稱 :param extraParams: 擴展參數 :param sign: 加密的sign :return: 返回點播的視頻的token """ ts = int(time.time() * 1000) # 時間戳 plain = { "userId": self.userId, 'videoId': videoId, 'ts': ts, 'viewerId': viewerId, 'viewerIp': viewerIp, 'viewerName': viewerName, 'extraParams': extraParams } # 按照官方文檔,將參數 按照ASCKII升序 key + value + key + value... + value 拼接 plain_sorted = {} key_temp = sorted(plain) for key in key_temp: plain_sorted[key] = plain[key] print(plain_sorted) plain_string = '' for k, v in plain_sorted.items(): plain_string += str(k) + str(v) print(plain_string) sign_data = self.secretkey + plain_string + self.secretkey # 取sign_data的md5的大寫 sign = self.tomd5(sign_data).upper() # 新的帶有sign的字典 plain.update({'sign': sign}) print('plain', plain) result = requests.post( url='https://hls.videocc.net/service/v1/token', headers={"Content-type": "application/x-www-form-urlencoded"}, # 一定要帶上這個請求頭 data=plain ).json() data = {} if isinstance(result, str) else result.get("data", {}) return {"token": data} polyv_video = PolyvPlayer()

from django.utils.deprecation import MiddlewareMixin class MyCorsMiddelware(MiddlewareMixin): def process_response(self, request, response): response["Access-Control-Allow-Origin"] = "*" if request.method == "OPTIONS": response["Access-Control-Allow-Headers"] = "*" return response

# --------- 保利威視頻注冊用戶id和key-------- POLYV_CONFIG = { 'userId': '您的id', # polyv 提供的服務器間的通訊驗證 'secretkey': '您的secret' # polyv 提供的接口調用簽名訪問的key }

from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from django.http import HttpResponse from utils.polyv import polyv_video import json class Polyv(APIView): def post(self, request): vid = request.data.get("vid") remote_addr = request.META.get("REMOTE_ADDR") user_id = 1 user_name = "test" verify_data = polyv_video.get_video_token(vid, remote_addr, user_id, user_name) return Response(verify_data["token"])
播放跑馬燈
以上步驟其實已經可以滿足大部分用戶了,但是有朋友發現了,這里還是可以用IDM直接下載啊:
所以接下來就要設置跑馬燈了
1.設置視頻授權跑馬燈
根據我的觀察,好像默認就開啟了跑馬燈的
2.設置一個授權地址
這個地址可以是本地的
3.創建一個html文件,名字隨意,存入如下代碼:
這里的域名部分,官方建議這樣設置,其實在后面我測試的時候,發現按照下面這個設置,兼容性不好,谷歌可以播放,IE瀏覽器放不了
4.url部分添加一個crossdomain.xml
指向test_bolyv視圖函數
視圖函數:
5.視頻接口/polyv添加get方法
這個get方法是保利威后台自動調用的,不是我們這邊服務端要用的,也不是客戶端要用的
user_name部分就是要顯示的跑馬燈數據
6.在定義的polyv類里添加方法:
get_play_key和get_resp
get_play_key是獲取sign的,注意這里的設置sign和上面獲取加密視頻的sign不太一樣
get_resp是做跑馬燈授權的:
7.引入新的js
這里他給的例子是用的不加密的方式
我們要播放加密視頻,當然還是得使用playsafe參數播放,在templates目錄下新建一個tests.html,代碼如下,我標注出來的就是添加的參數,同樣的,注意返回數據的層級
啟動項目:
通過pycharm虛擬一個客戶端出來,點擊那些瀏覽器圖標打開,我的電腦是windows,所以試了谷歌,火狐和IE
谷歌:谷歌默認打開是沒有聲音的,這是谷歌瀏覽器的策略問題,不是大問題,正常播放,且正常顯示跑馬燈
IE:
再次強調,如果在那個xml文件里,你如果按官方的建議設置成這樣:
然后你打開IE是播放不了的:
所以別按它建議的設置,就用默認的
火狐:
問題來了,就是這個問題,我折騰老久了,這個根本原因還是跨域請求問題,因為火狐瀏覽器默認安全性比較高,所以谷歌和IE可以,就是火狐不行,我查閱了很多,比如關閉火狐的跨域請求的,設置跨域請求的,設置了很多,還是沒用,最后使用了第三方庫django-cors-headers解決了,相關介紹安裝文章: 前后端分離djangorestframework——解決跨域請求 第5個方法
根據操作,重啟項目,火狐立馬播放:
然后現在你看我用IDM點下載:
我隨便選了一個:
提示:
最后再來個輔助測試,換個視頻id播放看看,換回這個視頻
火狐,谷歌,IE:
確實沒有問題了,這樣的操作是不是很6啊,我反正感覺很6
詳細的參數配置步驟就沒展開了,具體看官方文檔吧:傳送門
相關代碼:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src='https://player.polyv.net/script/polyvplayer.min.js'></script> <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script> </head> <body> <div id="player"></div> <script> var player = polyvObject('#player').videoPlayer({ wrap: '#player', width: 800, height: 533, forceH5: true, vid: '2f57a43618b400b4f9c84fcea9b103a8_2', code: 'myRandomCodeValue', playsafe: function (vid, next) { // 向后端發送請求獲取加密的token console.log(vid); axios.request({ url: "http://localhost:8000/polyv", method: "POST", data: { vid: vid } }).then(function (data) { console.log(data); next(data.data.token) }) } }); </script> </body> </html>

<cross-domain-policy> <allow-access-from domain="*.polyv.net"/> </cross-domain-policy>

from django.conf import settings import time import requests import json import hashlib class PolyvPlayer(object): userId = settings.POLYV_CONFIG['userId'] secretkey = settings.POLYV_CONFIG['secretkey'] def tomd5(self, value): """取md5值""" return hashlib.md5(value.encode()).hexdigest() # 獲取視頻數據的token def get_video_token(self, videoId, viewerIp, viewerId=None, viewerName='', extraParams='HTML5'): """ :param videoId: 視頻id :param viewerId: 看視頻用戶id :param viewerIp: 看視頻用戶ip :param viewerName: 看視頻用戶昵稱 :param extraParams: 擴展參數 :param sign: 加密的sign :return: 返回點播的視頻的token """ ts = int(time.time() * 1000) # 時間戳 plain = { "userId": self.userId, 'videoId': videoId, 'ts': ts, 'viewerId': viewerId, 'viewerIp': viewerIp, 'viewerName': viewerName, 'extraParams': extraParams } # 按照官方文檔,將參數 按照ASCKII升序 key + value + key + value... + value 拼接 plain_sorted = {} key_temp = sorted(plain) for key in key_temp: plain_sorted[key] = plain[key] print(plain_sorted) plain_string = '' for k, v in plain_sorted.items(): plain_string += str(k) + str(v) print(plain_string) sign_data = self.secretkey + plain_string + self.secretkey # 取sign_data的md5的大寫 sign = self.tomd5(sign_data).upper() # 新的帶有sign的字典 plain.update({'sign': sign}) print('plain', plain) result = requests.post( url='https://hls.videocc.net/service/v1/token', headers={"Content-type": "application/x-www-form-urlencoded"}, # 一定要帶上這個請求頭 data=plain ).json() data = {} if isinstance(result, str) else result.get("data", {}) return {"token": data} def get_play_key(self, vid, username, code, status, ts): """ :param vid: 視頻 vid :param username: 響應跑馬燈展示 :param code: 自定義參數 :param status: 是否可播放, 1、可播放 2、禁播 :param ts: 時間戳 :return: 返回跑馬燈視頻的key """ return self.tomd5("vid={}&secretkey={}&username={}&code={}&status={}&t={}".format( vid, self.secretkey, username, code, status, ts)).lower() @staticmethod def get_resp(status, username, sign, msg="授權暫未通過"): res_str = { "status": status, "username": username, "sign": sign, "msg": msg, "fontSize": "18", "fontColor": "0xFF0000", "speed": "50", "filter": "on", "setting": "2", "alpha": "0.7", "filterAlpha": "1", "filterColor": "0x3914AF", "blurX": "2", "blurY": "2", "tweenTime": "1", "interval": "3", "lifeTime": "3", "strength": "4", "show": "on" } return res_str polyv_video = PolyvPlayer()

from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from django.http import HttpResponse from utils.polyv import polyv_video import json class Polyv(APIView): def post(self, request): vid = request.data.get("vid") remote_addr = request.META.get("REMOTE_ADDR") user_id = 1 user_name = "test" verify_data = polyv_video.get_video_token(vid, remote_addr, user_id, user_name) return Response(verify_data["token"]) def get(self, request, *args, **kwargs): vid = request.query_params.get("vid", "") code = request.query_params.get("code", "") t = request.query_params.get("t", "") callback = request.query_params.get("callback", "") user_name = "test|1234565" status = 1 # username, code, status, t sign = polyv_video.get_play_key(vid, user_name, code, status, t) print(sign) res_str = polyv_video.get_resp(int(status), user_name, sign) res_str = json.dumps(res_str, ensure_ascii=False) if callback != "": ret = callback + "(" + res_str + ")" else: ret = res_str print(ret) return HttpResponse(ret) # 解決跨域 def test_bolyv(request): return render(request, "bolyv_test.html")

from django.contrib import admin from django.urls import path, re_path from app.views import test_bolyv from app.views import Polyv urlpatterns = [ path('admin/', admin.site.urls), path('polyv', Polyv.as_view()), # 這里要與客戶端url對應,最后有沒有【/】要統一 re_path(r'^crossdomain.xml', test_bolyv) ]

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app.apps.AppConfig', 'corsheaders', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', # 注意順序 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 'utils.middlewares.MyCorsMiddelware' # 這是我們自己定義的那個中間件 ] # --------- 保利威視頻注冊用戶id和key-------- POLYV_CONFIG = { 'userId': '您的id', # polyv 提供的服務器間的通訊驗證 'secretkey': '您的secret' # polyv 提供的接口調用簽名訪問的key } # 跨域增加忽略 CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_WHITELIST = ( '*' ) CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', )
總結
- 經過這前后幾個接入第三方平台的案例,發現其實都不算難,但是都有一些坑,必須要自己去實踐去研究了才能玩得會
- 火狐瀏覽器的安全級別默認很高,需要借助第三方庫來配置
- 跑馬燈視頻設置get請求的url必須和主邏輯API接口是同一個
- 遇到問題還是多研究,多理解,多分析