Spring MVC+MediaElement.js實現(xiàn)在WEB上播放MP4并支持拖移播放

導語

使用MediaElement.js,在WEB上可以播放MP4文件,功能強大当船,定制性強,支持拖移播放默辨。若文件在工程目錄下德频,則可以直接使用tomcat的DefaultServlet來處理,則MediaElement.js可以完美播放缩幸,若是自定義的壹置,則繼續(xù)往下看。

原理

播放器是采用分塊請求下載的表谊,斷點續(xù)傳的方式钞护,其原理如下:
http協(xié)議中,服務端實現(xiàn)斷點續(xù)傳首先需要讀取客戶端傳送的Range頭信息爆办,比如“Range: bytes=12583394-”這個就是指原來正在下載的文件需要從第12583394字節(jié)繼續(xù)下載难咕,然后我們利用Java.io.File的skip方法,舍棄掉原文件的前n個字節(jié),接著就繼續(xù)慢慢write吧余佃。暮刃。。
但是客戶端又是如何判斷服務端是否支持斷點續(xù)傳的呢爆土?主要就是Accept-Ranges和Content-Length頭信息椭懊。比如“Accept-Ranges:bytes”和“Content-Length:99999999”。有了這兩個頭信息步势,客戶端就認為服務端是支持斷點續(xù)傳的了氧猬。
然后需要注意的是,假如客戶端剛才由于某些原因坏瘩,暫停了下載狂窑,現(xiàn)在恢復的時候,就會如前所述桑腮,傳來Range頭信息泉哈,這時候,我們的response就需要設置一下狀態(tài)碼破讨,這里應該設置成206(詳細解釋請看http://en.wikipedia.org/wiki/List_of_HTTP_status_codes)丛晦,還有就是Content-Range頭信息,格式為“bytes x-(y-1)/y”提陶,x就是客戶端傳來的開始字節(jié)位置烫沙,y就是文件長度。

實現(xiàn)

1.JSP頁面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://"
            + request.getServerName() + ":" + request.getServerPort()
            + path + "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<link rel="stylesheet" href="vender/mediaelement/mediaelementplayer.css">
</head>
<body style="width: 100%; height: 100%;">
    <div class=warp">
        <div style="text-align: center;margin-top: 10px">
            <h2>${videoTitle}</h2>
        </div>

        <div style="margin: 0 auto;width: 1280px" data-options="border:false">
            <video src="${videoUrl}" width="1280" height="720"
                data-mejsoptions='{"stretching" : "auto","pluginPath" : "vender/mediaelement/", "alwaysShowControls": "true", "lang" : "zh-cn"}'></video>
        </div>
    </div>

        <script src="vender/jquery.js"></script>
    <script src="vender/mediaelement/mediaelement-and-player.js"></script>
    <script src="vender/mediaelement/lang/zh-cn.js"></script>
    <script type="text/javascript">
                // 設置語言為中文
        mejs.i18n.language('zh-CN');
                // 創(chuàng)建播放器
        $('video').mediaelementplayer({
            stretching : "auto",
            pluginPath : "vender/mediaelement/",
            alwaysShowControls : true,
            success : function(player, node) {
                player.play();
            }
        });
    </script>
</body>
</html>

2.Spring MVC的Controller

/**
     * 大文件分塊下載
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping("/storage/**")
    public void bigFileDownload(HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        String uri = URLDecoder.decode(request.getRequestURI(), "UTF-8");
        String filename = uri.substring(uri.indexOf(STORAGE) + STORAGE.length()
                + 1, uri.length());
        File downloadFile = fileService.getFile(filename); // 要下載的文件
        long fileLength = downloadFile.length();// 記錄文件大小
        long pastLength = 0;// 記錄已下載文件大小
        long toLength = 0;// 記錄客戶端需要下載的字節(jié)段的最后一個字節(jié)偏移量(比如bytes=27000-39000隙笆,則這個值是為39000)
        long contentLength = 0;// 客戶端請求的字節(jié)總量
        String rangeBytes = "";// 記錄客戶端傳來的形如“bytes=27000-”或者“bytes=27000-39000”的內(nèi)容

        // ETag header
        // The ETag is contentLength + lastModified
        response.setHeader("ETag",
                "W/\"" + fileLength + "-" + downloadFile.lastModified() + "\"");
        // Last-Modified header
        response.setHeader("Last-Modified",
                new Date(downloadFile.lastModified()).toString());

        if (request.getHeader("Range") != null) {// 客戶端請求的下載的文件塊的開始字節(jié)
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            log.info("request.getHeader(\"Range\")="
                    + request.getHeader("Range"));
            rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");
            if (rangeBytes.indexOf('-') == rangeBytes.length() - 1) {// bytes=969998336-
                rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
                pastLength = Long.parseLong(rangeBytes.trim());
                toLength = fileLength - 1;
            } else {// bytes=1275856879-1275877358
                String temp0 = rangeBytes.substring(0, rangeBytes.indexOf('-'));
                String temp2 = rangeBytes.substring(
                        rangeBytes.indexOf('-') + 1, rangeBytes.length());
                // bytes=1275856879-1275877358锌蓄,從第 1275856879個字節(jié)開始下載
                pastLength = Long.parseLong(temp0.trim());
                // bytes=1275856879-1275877358,到第 1275877358 個字節(jié)結束
                toLength = Long.parseLong(temp2);
            }
        } else {// 從開始進行下載
            toLength = fileLength - 1;
        }
        // 客戶端請求的是1275856879-1275877358 之間的字節(jié)
        contentLength = toLength - pastLength + 1;
        if (contentLength < Integer.MAX_VALUE) {
            response.setContentLength((int) contentLength);
        } else {
            // Set the content-length as String to be able to use a long
            response.setHeader("content-length", "" + contentLength);
        }
        WebApplicationContext webApplicationContext = ContextLoader
                .getCurrentWebApplicationContext();
        ServletContext servletContext = webApplicationContext
                .getServletContext();
        String contentType = servletContext.getMimeType(filename);
        if (null != contentType) {
            response.setContentType(contentType);
        }

        // 告訴客戶端允許斷點續(xù)傳多線程連接下載,響應的格式是:Accept-Ranges: bytes
        response.setHeader("Accept-Ranges", "bytes");
        // 必須先設置content-length再設置header
        response.addHeader("Content-Range", "bytes " + pastLength + "-"
                + toLength + "/" + fileLength);

        response.setBufferSize(2048);

        InputStream istream = null;
        OutputStream os = null;
        try {
            os = response.getOutputStream();
            istream = new BufferedInputStream(
                    new FileInputStream(downloadFile), 2048);
            try {
                copyRange(istream, os, pastLength, toLength);
            } catch (IOException ie) {
                /**
                 * 在寫數(shù)據(jù)的時候撑柔, 對于 ClientAbortException 之類的異常瘸爽,
                 * 是因為客戶端取消了下載,而服務器端繼續(xù)向瀏覽器寫入數(shù)據(jù)時铅忿, 拋出這個異常剪决,這個是正常的。
                 * 尤其是對于迅雷這種吸血的客戶端軟件檀训, 明明已經(jīng)有一個線程在讀取 bytes=1275856879-1275877358柑潦,
                 * 如果短時間內(nèi)沒有讀取完畢,迅雷會再啟第二個峻凫、第三個渗鬼。。荧琼。線程來讀取相同的字節(jié)段譬胎, 直到有一個線程讀取完畢肛循,迅雷會 KILL
                 * 掉其他正在下載同一字節(jié)段的線程, 強行中止字節(jié)讀出银择,造成服務器拋 ClientAbortException。
                 * 所以累舷,我們忽略這種異常
                 */
                // ignore
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (istream != null) {
                try {
                    istream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    protected void copyRange(InputStream istream, OutputStream ostream,
            long start, long end) throws IOException {

        long skipped = 0;
        skipped = istream.skip(start);

        if (skipped < start) {
            throw new IOException("skip fail: skipped=" + Long.valueOf(skipped)
                    + ", start=" + Long.valueOf(start));
        }

        long bytesToRead = end - start + 1;

        byte buffer[] = new byte[2048];
        int len = buffer.length;
        while ((bytesToRead > 0) && (len >= buffer.length)) {
            try {
                len = istream.read(buffer);
                if (bytesToRead >= len) {
                    ostream.write(buffer, 0, len);
                    bytesToRead -= len;
                } else {
                    ostream.write(buffer, 0, (int) bytesToRead);
                    bytesToRead = 0;
                }
            } catch (IOException e) {
                len = -1;
                throw e;
            }
            if (len < buffer.length)
                break;
        }

    }
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浩考,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子被盈,更是在濱河造成了極大的恐慌析孽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件只怎,死亡現(xiàn)場離奇詭異袜瞬,居然都是意外死亡,警方通過查閱死者的電腦和手機身堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門邓尤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贴谎,你說我怎么就攤上這事汞扎。” “怎么了擅这?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵澈魄,是天一觀的道長。 經(jīng)常有香客問我仲翎,道長痹扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任溯香,我火速辦了婚禮鲫构,結果婚禮上,老公的妹妹穿的比我還像新娘玫坛。我一直安慰自己芬迄,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布昂秃。 她就那樣靜靜地躺著禀梳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肠骆。 梳的紋絲不亂的頭發(fā)上算途,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音蚀腿,去河邊找鬼嘴瓤。 笑死扫外,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的廓脆。 我是一名探鬼主播筛谚,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼停忿!你這毒婦竟也來了驾讲?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤席赂,失蹤者是張志新(化名)和其女友劉穎吮铭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颅停,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡谓晌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了癞揉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纸肉。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喊熟,靈堂內(nèi)的尸體忽然破棺而出毁靶,到底是詐尸還是另有隱情,我是刑警寧澤逊移,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布预吆,位于F島的核電站,受9級特大地震影響胳泉,放射性物質發(fā)生泄漏拐叉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一扇商、第九天 我趴在偏房一處隱蔽的房頂上張望凤瘦。 院中可真熱鬧,春花似錦案铺、人聲如沸蔬芥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笔诵。三九已至,卻和暖如春姑子,著一層夾襖步出監(jiān)牢的瞬間乎婿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工街佑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谢翎,地道東北人捍靠。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像森逮,于是被迫代替她去往敵國和親榨婆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • API定義規(guī)范 本規(guī)范設計基于如下使用場景: 請求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請求頻率非常高褒侧,建議使用雙通...
    有涯逐無涯閱讀 2,519評論 0 6
  • 斷點續(xù)傳概述 斷點續(xù)傳就是從文件上次中斷的地方開始重新下載或上傳數(shù)據(jù)良风,而不是從文件開頭。(本文的斷點續(xù)傳僅涉及下載...
    Crazy2015閱讀 649評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理璃搜,服務發(fā)現(xiàn),斷路器鳞上,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 灰蒙蒙的天这吻,給人一種無形的壓力,讓人喘不過氣來篙议,聽著耳機里傳來的輕音樂唾糯,屏蔽掉了此刻的喧鬧,完全沉浸在自己...
    晴妁閱讀 192評論 2 6
  • 金指尖的花園閱讀 186評論 0 3