Подавать файл mp4 через coldfusion и играть с jwplayer

У меня есть веб-приложение в coldfusion, которое записывает видео и предоставляет видео пользователю.

Видео работает нормально в браузерах Android и настольных ПК, но выдает ошибку «Ошибка загрузки носителя: файл не может быть воспроизведен» в IOS.

Вот мой код 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">

Я попробовал какое-то решение, добавив заголовок. Но это не работает. Может ли кто-нибудь помочь мне разобраться в проблеме.


person Abdul Rauf    schedule 28.03.2017    source источник
comment
Если вы дадите нам доступ к сайту (по крайней мере, к интерфейсу), мы, вероятно, сможем помочь в его диагностике. Я реализовал проекты с CF, видео и jwplayer вместе.   -  person Jules    schedule 28.03.2017
comment
Можно ли вообще воспроизводить файлы вдали от вашего собственного приложения на вашем устройстве iOS? Я имею в виду, просто убедитесь, что сами данные файла приемлемы для воспроизведения iOS. Сохраните один файл с сервера в хранилище и попробуйте воспроизвести в каком-нибудь приложении медиаплеера ... что происходит? Протестировали тот же файл на другом устройстве iOS (характеристики / мощность)?   -  person VC.One    schedule 29.03.2017
comment
@ VC.One. Я добавил другую демонстрацию на jwplayer-techy.fwd.wf. На IOS работает только один файл через hlshtml: true. Вы знаете, как я могу использовать этот параметр для запуска mp4 из файла?   -  person Abdul Rauf    schedule 29.03.2017
comment
@ Джулс. Вот URL-адрес тестового примера с использованием моего кода jwplayer-techy.fwd.wf. Пожалуйста, посмотри   -  person Abdul Rauf    schedule 29.03.2017
comment
Я сразу вижу, что тип контента CFM / MP4 передается как video / mp4; charset = UTF-8, тогда как MP4 с прямым обслуживанием - это просто video / mp4.   -  person Jules    schedule 29.03.2017
comment
@ Джулс. Спасибо за ваш ответ. не могли бы вы сообщить мне, как я могу исправить тип содержимого   -  person Abdul Rauf    schedule 30.03.2017
comment
@AbdulRauf также я заметил, что ваш mp4 на самом деле является файлом f4v с расширением, только что измененным на .mp4. Я думаю, это то, что мешает iOS принимать его.   -  person VC.One    schedule 30.03.2017
comment
@ VC.One. это может быть проблемой. Позвольте мне тоже это проверить. :)   -  person Abdul Rauf    schedule 30.03.2017
comment
@ VC.One. Я заметил, что есть одно поведение. Если вы видите последние два примера на jwplayer-techy.fwd.wf. Вы заметите, что один идет с //vr.jwplayer.com/content/AgqYcfAT/AgqYcfAT-8yQ1cYbs.mp4, отлично работает в IOS. Я загрузил его и запустил локально, но тот же файл не работает в IOS. Я думаю, мне нужно что-то изменить в IIS, как это обрабатывается. Если у вас есть предложения. Пожалуйста, дай мне знать.   -  person Abdul Rauf    schedule 30.03.2017
comment
Сначала убедитесь, что вы действительно обслуживаете правильный MP4 для @ VC.One.   -  person Jules    schedule 31.03.2017
comment
@ Джулс. Да, я использую hdfvr для записи и API транскодера wowza для преобразования файла в mp4. Проблема в том, что video.cfm не отправляет контент частично.   -  person Abdul Rauf    schedule 01.04.2017
comment
@AbdulRauf У меня нет доступа к устройству iOS для проверки. Кроме того, я не очень разбираюсь в серверах, поэтому не могу посоветовать IIS. Все, что я знаю, это ваши байты f4v. Также я думаю, что Wowza участвует (?), Если да, то посмотрите, можете ли вы настроить параметры вывода Wowza или IIS, чтобы получить mp4.   -  person VC.One    schedule 02.04.2017
comment
@ VC.One. Спасибо. Я понял это. Я опубликовал свой ответ.   -  person Abdul Rauf    schedule 03.04.2017


Ответы (1)


Я могу решить свою проблему. iOS использует частичный заголовок контента для запуска видео. Спасибо rickward за это прекрасное решение: Доставка мультимедиа на iPhone и iPad. Я внес небольшие изменения, и это начало работать на меня.

Вот последний файл 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>
person Abdul Rauf    schedule 01.04.2017
comment
Рад, что ты это понял. Небольшое предложение, вы, вероятно, могли бы упростить этот первый <cfelse> и просто использовать <cfcontent>. Кроме того, не забудьте var/local охватить все переменные функции, включая context, response, out и т. Д. - person Leigh; 02.04.2017
comment
@ Ли. Спасибо за ваше предложение. Я угрюмо изменю его в соответствии с вашими предложениями. :) - person Abdul Rauf; 03.04.2017