作業ソリューション:
最終的に、シークをサポートし、あまり手間をかけないソリューションを見つけることができました。基本的にHttpForwardRequest
、HTTP ヘッダーなどの他の初期サーブレット要求の詳細を保持しながら、指定されたメディア URL に新しい HTTP 要求を発行することによって、要求処理を Web サーバーに委任するコンポーネントを作成しました。Web サーバーの応答は、サーブレットの応答出力ストリームにパイプされます。
私たちの場合、Web サーバー (ISS 7.0) は既に HTTP ストリーミングの方法を知っているので、これだけを行う必要があります。
注:試してみましgetRequestDispatcher('some_media_url').forward(...)
たが、正しいヘッダーを持つメディア ファイルを提供できないようです。
HttpForwardRequest
コード:
<cfcomponent output="no">
<cffunction name="init" access="public" returntype="HttpForwardRequest" output="no">
<cfargument name="url" type="string" required="yes" hint="The URL to which the request should be forwarded to.">
<cfargument name="requestHeaders" type="struct" required="yes" hint="The HTTP request headers.">
<cfargument name="response" type="any" required="yes" hint=" The servlet's response object.">
<cfargument name="responseHeaders" type="struct" required="no" default="#{}#" hint="Custom response headers to override the initial request response headers.">
<cfset variables.instance = {
url = arguments.url,
requestHeaders = arguments.requestHeaders,
response = arguments.response,
responseHeaders = arguments.responseHeaders
}>
<cfreturn this>
</cffunction>
<cffunction name="send" access="public" returntype="void" output="no">
<cfset var response = variables.instance.response>
<cfset var outputStream = response.getOutputStream()>
<cfset var buffer = createBuffer()>
<cftry>
<cfset var connection = createObject('java', 'java.net.URL')
.init(variables.instance.url)
.openConnection()>
<cfset setRequestHeaders(connection)>
<cfset setResponseHeaders(connection)>
<cfset var inputStream = connection.getInputStream()>
<cfset response.setStatus(connection.getResponseCode(), connection.getResponseMessage())>
<cfloop condition="true">
<cfset var bytesRead = inputStream.read(buffer, javaCast('int', 0), javaCast('int', arrayLen(buffer)))>
<cfif bytesRead eq -1>
<cfbreak>
</cfif>
<cftry>
<cfset outputStream.write(buffer, javaCast('int', 0), bytesRead)>
<cfset outputStream.flush()>
<!---
Connection reset by peer: socket write error
The above error occurs when users are seeking a video.
That is probably normal since I assume the client (e.g. Window Media Player)
closes the connection when seeking.
--->
<cfcatch type="java.net.SocketException">
<cfbreak>
</cfcatch>
</cftry>
</cfloop>
<cffinally>
<cfif not isNull(inputStream)>
<cfset inputStream.close()>
</cfif>
<cfif not isNull(connection)>
<cfset connection.disconnect()>
</cfif>
</cffinally>
</cftry>
</cffunction>
<cffunction name="setRequestHeaders" access="private" returntype="void" output="no">
<cfargument name="connection" type="any" required="yes">
<cfset var requestHeaders = variables.instance.requestHeaders>
<cfloop collection="#requestHeaders#" item="local.key">
<cfset arguments.connection.setRequestProperty(key, requestHeaders[key])>
</cfloop>
</cffunction>
<cffunction name="setResponseHeaders" access="private" returntype="void" output="no">
<cfargument name="connection" type="any" required="yes">
<cfset var response = variables.instance.response>
<cfset var responseHeaders = variables.instance.responseHeaders>
<cfset var i = -1>
<!--- Copy connection headers --->
<cfloop condition="true">
<cfset i = javaCast('int', i + 1)>
<cfset var key = arguments.connection.getHeaderFieldKey(i)>
<cfset var value = arguments.connection.getHeaderField(i)>
<cfif isNull(key)>
<cfif isNull(value)>
<!--- Both, key and value are null, break --->
<cfbreak>
</cfif>
<!--- Sometimes the key is null but the value is not, just ignore and keep iterating --->
<cfcontinue>
</cfif>
<cfset setResponseHeader(key, value)>
</cfloop>
<!--- Apply custom headers --->
<cfloop collection="#responseHeaders#" item="key">
<cfset setResponseHeader(key, responseHeaders[key])>
</cfloop>
</cffunction>
<cffunction name="setResponseHeader" access="private" returntype="void" output="no">
<cfargument name="key" type="string" required="yes">
<cfargument name="value" type="string" required="yes">
<cfset var response = variables.instance.response>
<cfif arguments.key eq 'Content-Type'>
<cfset response.setContentType(arguments.value)>
<cfelse>
<cfset response.setHeader(arguments.key, arguments.value)>
</cfif>
</cffunction>
<cffunction name="createBuffer" access="private" returntype="any" output="no">
<cfreturn repeatString("12345", 1024).getBytes()>
</cffunction>
</cfcomponent>
cf_streamurl
コード:
<cfparam name="attributes.url" type="url">
<cfif thisTag.executionMode neq 'start'>
<cfexit>
</cfif>
<cfset pageContext = getPageContext()>
<cfset requestHeaders = {
'Authorization' = 'Anonymous'
}>
<cfset structAppend(requestHeaders, getHTTPRequestData().headers, false)>
<cfset pageContext.setFlushOutput(false)>
<!--- Forward the request to IIS --->
<cfset new references.cfc.servlet.HttpForwardRequest(
attributes.url,
requestHeaders,
pageContext.getResponse().getResponse()
).send()>
cf_streamurl
次に、次のようなカスタム タグを使用できます。
<cf_streamurl url="http://sh34lprald94/media_stream/unprotected/trusts.mp4"/>
重要: 現在のところ、匿名認証のみがサポートされています。
最初の半作業の試み (歴史的な目的のみ):
応答パケットの HTTP ヘッダーを検査し、IIS にメディア ファイルをサーバーさせるときに返される MIME タイプを調べることで、ニーズに合ったソリューション (実際には非常に単純なソリューション) を見つけました。
問題は、ColdFusion を使用してファイル コンテンツをブラウザに提供しようとするときに、 Window Media ServicesのMIME タイプの 1 つを使用して、ブラウザが処理を Window Media Player に直接委任するよう強制する必要があったことです (これにより、コンテンツをストリーミングできるようになります)。ファイル)。
File extension MIME type
.asf video/x-ms-asf
.asx video/x-ms-asf
.wma audio/x-ms-wma
.wax audio/x-ms-wax
.wmv audio/x-ms-wmv
.wvx video/x-ms-wvx
.wm video/x-ms-wm
.wmx video/x-ms-wmx
.wmz application/x-ms-wmz
.wmd application/x-ms-wmd
この問題を解決するための最初のステップは、ファイルの拡張子に基づいて MIME タイプを正しく解決する関数を作成することでした。IIS には既にその知識がありますが、MIME レジストリを照会する方法はまだ見つかりません。
注:wmsMimeTypes
は、WMS MIME タイプを検索するためのマップとして使用される構造体です。
<cffunction name="getMimeType" access="public" returntype="string">
<cfargument name="fileName" type="string" required="yes">
<cfset var mimeType = 'application/x-unknown'>
<cfset var ext = this.getFileExtension(arguments.fileName)>
<cfif structKeyExists(this.wmsMimeTypes, ext)>
<cfreturn this.wmsMimeTypes[ext]>
</cfif>
<!--- TODO: Is there a way to read the IIS MIME registry? --->
<cfregistry action="get" branch="HKEY_CLASSES_ROOT\.#ext#" entry="Content Type" variable="mimeType">
<cfreturn mimeType>
</cffunction>
次に、ColdFusion を使用して、ファイル全体をメモリにロードせずにファイルをクライアントstream
にストリーミングするにある実装に基づいて、ストリーミング プロセスをカプセル化する以下のようなメソッドを実装しました。
注: も動作しcfcontent
ますが、特にブラウザにフラッシュする前にファイル全体をメモリにロードするため、リソースを大量に消費するため、非常に非効率的であると読みました。
<cffunction name="stream" access="public" returntype="void">
<cfargument name="file" type="string" required="yes">
<cfargument name="mimeType" type="string" required="no">
<cfscript>
var fileName = getFileFromPath(arguments.file);
var resolvedMimeType = structKeyExists(arguments, 'mimeType')? arguments.mimeType : this.getMimeType(fileName);
var javaInt0 = javaCast('int', 0);
var response = getPageContext().getResponse().getResponse();
var binaryOutputStream = response.getOutputStream();
var bytesBuffer = repeatString('11111', 1024).getBytes();
var fileInputStream = createObject('java', 'java.io.FileInputStream').init(javaCast('string', getRootPath() & arguments.file));
getPageContext().setFlushOutput(javaCast('boolean', false));
response.resetBuffer();
response.setContentType(javaCast('string', resolvedMimeType));
try {
while (true) {
bytesRead = fileInputStream.read(bytesBuffer, javaInt0, javaCast('int', arrayLen(bytesBuffer)));
if (bytesRead eq -1) break;
binaryOutputStream.write(bytesBuffer, javaInt0, javaCast('int', bytesRead));
binaryOutputStream.flush();
}
response.reset();
} finally {
if (not isNull(fileInputStream)) fileInputStream.close();
if (not isNull(binaryOutputStream)) binaryOutputStream.close();
}
</cfscript>
</cffunction>
ヘッダーを設定しないでください。そうしないContent-Disposition
と、コントロールを WMP に委譲する代わりに、ブラウザーがファイルをダウンロードします。
注: @Miguel-F が提案した記事に記載されているように、Web サーバーにファイルをクライアント (または使用した CF ソリューション) にストリーミングさせることは、メディア サーバーを使用するほど効率的ではありません。
主な欠点: 以前の実装ではシークがサポートされていないため、実際にはソリューションがほとんど使用できなくなる可能性があります。