通过 coldfusion 提供 mp4 文件并使用 jwplayer 播放

Serve mp4 file through coldfusion and play with jwplayer

我在 coldfusion 中有一个 Web 应用程序,可以录制视频并向用户提供视频。

该视频在 android 和桌面浏览器上运行良好,但在 IOS 中出现错误 "Error loading media: File could not be played"。

这是我的 JWPlayer 代码,目前可以使用。

jwplayer("element").setup({
  file: "/video.cfm?token=4514_129_9B2F727D-5056-A85D-6EBE3E48FC2AB9C6",
  image: "path/to/image",
  width: 450,
  height: 360,
  type: "mp4",
  logo: {
     file: 'path/to/logo',
     link: 'example.com',
     hide : true
  }
});

这是我video.cfm验证后发送到服务器的mp4。

<cfset videoFile = 'path\to\file'>
<cfset fileInfo = GetFileInfo(videoFile)>
<cfset length = fileInfo.size>
<cfset start = 0>
<cfset end = fileInfo.size - 1>
<cfheader name="Content-type" value="video/mp4">
<cfheader name="Accept-Ranges" value="0-#length#">
<cfheader name="Content-Range" value="bytes #start#-#end#/#fileInfo.size#">
<cfheader name="Content-Length" value="#length#">
<cfcontent file="#videoFile#" type="video/mp4">

我已经通过添加一些 header 尝试了一些解决方案。但这是行不通的。谁能帮我解决这个问题。

我能够解决我的问题。 iOS 使用部分内容 header 到 运行 视频。感谢 rickward 提供这个可爱的解决方案:Media Delivery to iPhones and iPads。我做了一些小改动,它开始为我工作。

这是最终的 video.cfm 文件。

<cfset videoPath = 'path\to\mp4\file'>
<cfif FileExists(videoPath)>
    <cfset fileInfoVar = GetFileInfo(videoPath)>
    <cfheader name="Last-Modified" value="#fileInfoVar.Lastmodified#">
    <cfheader name="ETag" value="#hash(videoPath, 'MD5')#">
    <cfheader name="Content-Location" value="http://example.com/video.cfm">

    <cfif structKeyExists(GetHttpRequestData().headers, 'Range')>
        <cfset rangeDownload(videoPath)>
    <cfelse>
        <cffile action="readbinary" file="#videoPath#" variable="theData">
        <cfscript>
            context = getPageContext();
            context.setFlushOutput(false);
            response = context.getResponse().getResponse();
            response.setContentType("video/mp4");
            response.setContentLength(arrayLen(theData));

            out = response.getOutputStream();
            out.write(theData);
            out.flush();
            out.close();
        </cfscript>
    </cfif>
</cfif>

<cffunction name="rangeDownload" returnType="void" output="yes">
    <cfargument name="file" type="string" required="true" hint="path to file">

    <cfset var l = {}>
    <cfset l.request = GetHttpRequestData()>

    <cffile action="readbinary" file="#ARGUMENTS.file#" variable="l.theData">

    <cfset l.size = arrayLen(l.theData)>
    <cfset l.length = l.size>
    <cfset l.start  = 0>
    <cfset l.end = l.size - 1>

    <!--- Now that we've gotten so far without errors we send the accept range header
    /* At the moment we only support single ranges.
     * Multiple ranges requires some more work to ensure it works correctly
     * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
     *
     * Multirange support annouces itself with:
     * header('Accept-Ranges: bytes');
     *
     * Multirange content must be sent with multipart/byteranges mediatype,
     * (mediatype = mimetype)
     * as well as a boundry header to indicate the various chunks of data.
     */
    --->
    <cfheader name="Accept-Ranges" value="0-#l.length#">
    <!---<cfheader name="Accept-Ranges" value="bytes"> --->
    <!---
      multipart/byteranges
      http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2 --->
    <cfif structKeyExists(l.request.headers, 'Range')>

        <cfset l.c_start = l.start>
        <cfset l.c_end = l.end>

        <!--- Extract the range string --->
        <cfset l.range = ListGetAt(l.request.headers.range, 2, '=')>
        <!--- Make sure the client hasn't sent us a multibyte range --->
        <cflog file="rangeDownload" text="#l.range#" />
        <cfif l.range contains ','>
            <!--- (?) Should this be issued here, or should the first
             range be used? Or should the header be ignored and
             we output the whole content?
            --->
            <cfheader statusCode = "416" statusText = "Requested Range Not Satisfiable">
            <cfheader name="Content-Range" value="bytes #l.start#-#l.end#/#l.size#">
            <!--- (?) Echo some info to the client? --->
            <cfabort>
        </cfif>
        <!--- If the range starts with an '-' we start from the beginning
            If not, we forward the file pointer
            And make sure to get the end byte if specified --->
        <cfif Left(l.range, 1) eq '-'>
        <!--- The n-number of the last bytes is requested --->
            <cfset l.c_start = l.size - Mid(l.range, 2, Len(l.range))>
        <cfelse>
            <cfset l.rangeArray = ListToArray(l.range, '-')>
            <cfset l.c_start = l.rangeArray[1]>
            <cfif ArrayLen(l.rangeArray) eq 2 and val(l.rangeArray[2]) gt 0>
                <cfset l.c_end = l.rangeArray[2]>
            <cfelse>
                <cfset l.c_end = l.size>
            </cfif>
        </cfif>
        <!---
        /* Check the range and make sure it's treated according to the specs.
         * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
         */
        // End bytes can not be larger than l.end. --->
        <cfif l.c_end gt l.end>
            <cfset l.c_end = l.end>
        </cfif>

        <!--- Validate the requested range and return an error if it's not correct. --->
        <cfif l.c_start gt l.c_end || l.c_start gt (l.size - 1) || l.c_end gte l.size>
            <cfheader statusCode = "416" statusText = "Requested Range Not Satisfiable">
            <cfheader name="Content-Range" value="bytes #l.start#-#l.end#/#l.size#">
            <!--- (?) Echo some info to the client? --->
            <cfabort>
        </cfif>

        <cfset l.start = l.c_start>
        <cfset l.end = l.c_end>
        <cfset l.length = l.end - l.start + 1><!--- Calculate new content length --->


        <cfscript>
            context = getPageContext();
            context.setFlushOutput(false);
            response = context.getResponse().getResponse();
            response.setContentType("video/mp4");
            response.setContentLength(l.length);
        </cfscript>
        <cfheader statusCode = "206" statusText = "Partial Content">

    </cfif>

    <!--- Notify the client the byte range we'll be outputting --->
    <cfheader name="Content-Range" value="bytes #l.start#-#l.end#/#l.size#">
    <cfheader name="Content-Length" value="#l.length#">

    <cfscript>
        // Start buffered download
        out = response.getOutputStream();
        // write the portion requested
        out.write(l.theData, javacast('int', l.start), javacast('int', l.length));
        out.flush();
        out.close();
    </cfscript>
</cffunction>