關於視頻播放、視頻切片、跨域訪問視頻
前言
最近在着手部署上線做的一個視頻網站,當我們部署到雲服務器上后並開始測試視頻觀看並發量,發現了一個很嚴重的問題:帶寬不足。9 或 10 個人同時觀看視頻的時候,就會出現有些用戶加載不了視頻的問題。
我們的雲服務器是 1塊錢的騰訊雲,本身帶寬就很低了,所以肯定沒有足夠大的帶寬來支撐這么大的流量傳輸。這對於一個視頻網站來說,帶寬這個問題是非常致命的。在不能改變帶寬的條件下,於是我們去找了一些方法來提高性能。
分析不足
我們的視頻網站使用的內置 Flash 播放器來播放視頻,用戶觀看視頻的時候是直接完全加載整個視頻的(不管你看了多久),從一開始播放就開始加載,並且並不會因為用戶暫停而暫停加載, 它是一直持續加載直到加載完全的。對於絕大多數用戶來說,他們不一定會把視頻看完,如果是加載一個小視頻,那還沒有什么大問題,但如果是加載一個大視頻的話,這就會浪費的大量的流量,並且加載過程會持續占用帶寬,使得用戶量多的時候,視頻加載就會出現問題。
修改第一步
了解到這個問題之后,我們去看了別人的視頻網站是如何撐起高用戶量的,在視頻播放的時候,我們發現它們並不是一開始就完全加載視頻的,而是一段段的加載,去搜索之后發現這是一種切片的技術,用於控制流量傳輸。具體的切片的原理可參看 http://www.cnblogs.com/flash3d/archive/2013/11/02/3403109.html。
了解了切片技術之后,我們於是就開始在我們項目中應用切片的技術,我們使用的是 ffmpeg 來對視頻進行切片。方法就是在程序中調用 ffmpeg 程序,然后調用切片命令對我們的視頻進行切片,生成 m3u8 文件和 ts 文件,然后使用 flash 播放器播放,能夠看到的確能夠一段段的加載視頻。
修改到這一步,這似乎解決了我們的問題,但是新的問題又出現了,我們發現當我們對大視頻進行切片的時候,服務器的內存會占用很大,至於為什么會占用那個大,我們猜想可能是因為對視頻切片時,ffmpeg 把整個視頻加載到內存,所以導致內存占用高。當同時對多個視頻進行切片的時候,服務器就炸掉了。於是我們又尋求新的方法去解決。
修改第二步
因為沒想到好的方法去解決本地切片內存占用問題,於是我們使用了新的途徑去存儲播放視頻,就是使用雲端存儲來存儲視頻,我們選用的是七牛雲服務器來存儲。它也提供了不同語音的 SDK 供開發者參考。
使用七牛雲,我在我的 Java Web 項目里面導進必須的 4 個包,以及編寫了上傳視頻並進行切片預處理的工具類。剛開始使用的時候也遇到許多問題。
問題1:上傳不同格式的視頻,有些播放不了
一開始,我們對任何格式的視頻都調用同一個切片命令,以為會生成同一種格式的視頻文件。但是當我們上傳的 MP4 格式的視頻,切片上傳后,直接在瀏覽器輸入外鏈(文件的訪問鏈接),此時能夠正常播放;但是上傳 avi 或者 flv 格式的視頻,上傳切片后,直接輸入外鏈會變成下載文件。
當時一直想不通這個問題的原因,因為明明都是調用了同一個命令切片,按理來說應該格式是一樣的,但是卻出現不同的行為。后來通過七牛雲的問答平台尋求解決方案,才發現如果在上傳的時候,沒有使用 saveas 參數對結果另存為 xxx.m3u8 格式,他還是任然會以原有的格式去保存。所以基於瀏覽器對不同視頻格式的支持,對 mp4、avi、flv等格式的視頻則出現不同的效果
問題2: 使用外鏈播放出現跨域拒絕的問題
在我們解決完視頻上傳的問題之后,在播放器通過外鏈來播放視頻的時候,發現出現跨域被拒絕的問題 (ERROR:HLSError(code/url/msg)=1tp:Cannot load M3U8: crossdomain access denied:Error #2048),google 了問題,發現原來Flash 播放器在加載跨域視頻時,會先去加載雲端的 corssdomain.xml 文件,然后判斷是否被允許加載。
解決方法:需要在七牛雲端上傳 crossdomian.xml 文件
<cross-domain-policy>
<allow-access-from domain="*"/>
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>
Java 七牛雲上傳視頻的工具類
基於七牛雲的 API,寫了一個上傳視頻並切片的接口,可供參考;如有錯 ,可一起討論
該接口的配置信息采用配置文件 video.properties 來加載,具體的配置文件配置內容為:
access_key=your access key
secert_key=your secert key
bucketname=你的存儲空間
pipeline=你的多媒體處理隊列名
fops=avthumb/m3u8/noDomain/1/vb/500k/t/120(這是切片命令)若是要做其他處理,請參照七牛雲 SDK
domain=你的七牛雲映射域名
public class QiNiuUtil {
private static String DEFAULT_PROPERTIES = "video.properties";
private static Properties properties = new Properties();
static {
String path = QiNiuUtil.class.getResource("/").toString();
path = path.substring(6, path.length() - 8) + DEFAULT_PROPERTIES;
System.out.println(path);
try {
FileInputStream fileInputStream = new FileInputStream(path);
properties.load(fileInputStream);
System.out.println(properties.toString());
} catch (IOException e) {
System.out.println("配置文件不存在,加載配置文件失敗");
}
}
public static String domian = properties.getProperty("domain");
private static String ACCESS_KEY = properties.getProperty("access_key");
private static String SECRET_KEY = properties.getProperty("secert_key");
// 要上傳的空間
private static String bucketname = properties.getProperty("bucketname");
// 設置切片操作參數
private static String fops =properties.getProperty("fops");
// 設置轉碼的隊列
private static String pipeline = properties.getProperty("pipeline");
//密鑰配置
private static Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
//創建上傳對象
private static UploadManager uploadManager = new UploadManager();
//上傳策略中設置persistentOps字段和persistentPipeline字段
public static String getUpToken(String pfops){
return auth.uploadToken(bucketname,null,3600,new StringMap()
.putNotEmpty("persistentOps", pfops)
.putNotEmpty("persistentPipeline", pipeline), true);
}
public static boolean upload(byte[] data, String key) throws IOException{
Response res = null;
try {
// 調用put方法上傳
// 指定文件以 m3u8 格式另存
String urlbase64 = UrlSafeBase64.encodeToString(bucketname + ":" + key + ".m3u8");
res = uploadManager.put(data, key, getUpToken(fops + "|saveas/"+ urlbase64));
//打印返回的信息
System.out.println(res.bodyString());
} catch (QiniuException e) {
Response r = e.response;
// 請求失敗時打印的異常的信息
System.out.println(r.toString());
try {
//響應的文本信息
System.out.println(r.bodyString());
} catch (QiniuException e1) {
//ignore
}
}
return res.isOK();
}
}
