作為客戶端安裝的媒體播放器,Java Media Framework并沒有給人很深的印象。它僅僅支持一部分媒體類型,而這些類型只是其他播放系統(tǒng)比如說Windows Media Player和QuickTime支持類型的子集。
但是從內容供應商的觀點來看,故事就變的有趣多了:JMF在所有的java模式下都可以適用,這樣我們在部署媒體的時候客戶端不需要任何媒體播放技術――只需要一個J2SE的運行環(huán)境就可以了。舉例來說,工程巨人Robert X. Cringely最近宣布一項計劃: 他們將提供一個被稱為"NerdTV"的純java的MPEG-4 系統(tǒng),這個系統(tǒng)不需要任何客戶端的預安裝。
更進一步來說,我們還可以利用.jar格式來把解碼器和媒體文件打包成一個文件,以此,從效果上來說創(chuàng)建了一個"自播放電影",與壓縮系統(tǒng)WinZip和StuffIt可以創(chuàng)建自擴展歸檔文件的方式相似。
實現(xiàn)的策略分三步走:
1.使JMF可以播放在.jar文件里的媒體文件。
2.創(chuàng)建一個精簡型的.jar 文件,只包括JMF里必須播放本地媒體文件的部分。
3.把代碼和媒體文件都放進.jar里,創(chuàng)建一個合適的manifest 文件來支持雙擊。
JMF基本放像功能的關鍵在于得到一個可以解碼和播放媒體文件的播放器。一般來說,這個動作通過使用管理器來得到一個合適的數(shù)據(jù)源,這個數(shù)據(jù)源在放像模式下同時提供媒體流和關于媒體流的元數(shù)據(jù),比如說媒體文件的格式。管理器接著找到一個播放器來處理這個數(shù)據(jù)源。在兩種情形下,管理器把一系列的包前綴(javax.media,com.ibm.media等等)綁定在反射機制上來尋找合適的類,在某種情形下拋出錯誤,比如說當找到的播放器不能接受提供的數(shù)據(jù)源。
接下來管理器沒有更多的辦法,通常只是查看URL里的協(xié)議和文件擴展名,所以它可以很容易的知道如何處理file&:///Users/cadamson/mymp3stash/some.mp3 ,卻不知道如何處理jar:file&:/Users/cadamson/dev/jmftests/spmovie-old/src/gatsbymovie.jar!/movie/themovie.mov。
為了讓事情變得更簡單,我們來寫一個數(shù)據(jù)源,或者更確切的說,寫一個牽引式數(shù)據(jù)源。它的職責是為管理器描速數(shù)據(jù)源本身。JarEntryDataSource里的方法都很簡單;但是有很多,因為我們提供的PullSourceStream需要使用很多接口的實現(xiàn)。
這種做法看起來有些不妥――類是通過文件的擴展名來返回"內容類型"的。它類似于MIME類型,只是它用句點而不是用斜杠來構建(MIME類型video/mpeg 轉換為video.mpeg,這樣管理器可以找到包com.sun.media.codec.video.mpeg )。下面是簡單的實現(xiàn):
public String getContentType() {
try {
URL url = getLocator().getURL();
String urlFile = url.getFile();
if (urlFile.endsWith(".mov"))
return "video.quicktime";
else if (urlFile.endsWith(".mpg"))
return "video.mpeg";
else if (urlFile.endsWith(".avi"))
// Manager needs '_' insted of '-'
return "video.x_msvideo";
else
return "unknown";
} catch (MalformedURLException murle) {
return "unknown";
}
}
另一個令人頭疼的問題是JMF的源代碼(目前從Sun的網(wǎng)站拿走了不過很快就會放上去)表明如果提供的流是Seekable,(一個提供隨機訪問方法seek()的接口) 缺省的播放器只能播放一個QuickTime的數(shù)據(jù)源。JarEntryDataSource的解決策略是在尋找點在媒介流前面的情況下使用InputStream.skip()。如果尋找點在當前讀取點(被稱為tellPoint因為這個值是由方法Seekable.tell()返回的)的后面,必須關閉InputStream,重開,然后跳到尋找點。它使用一個內部的thoroughSkip()方法來保證我們是真正的結束。
public long seek (long position) {
try {
if (position > tellPoint) {
thoroughSkip (position - tellPoint);
} else {
close();
open();
thoroughSkip (position);
}
return tellPoint;
} catch (IOException ioe) {
return 0; // bogus...
}
}
使用這個類,管理器可以找到一個可用的播放器來播放.jar里的.mov或者.avi 格式的文件。在我們的例子里TinyPlayer使用ClassLoader.getResource()來在classpath里找到movie/themovie.mov或者movie/themovie.avi。當classpath只包括.jar文件的時候,我們就實現(xiàn)了自播放。
準備一個合適的.jar文件的第一步是使用JMF的工具創(chuàng)建一個僅僅包含播放所必須的類的jar包,忽略那些流化,尋找,轉碼和其他任何不是骨架播放器所需要的功能。
不幸的是,Sun在純java版本的JMF里沒有包括jmfcustomizer的幫助文件,但是我們可以很容易的描速出定制所需要的一系列頁面。
媒體源和媒體接收器: 選擇"媒體文件"和"播放"
協(xié)議:"文件"
媒體格式:"QuickTime (.mov)" 和 "Avi."
解碼器:不論你計劃用什么,最可能的是"A-law," "U-law," 或者音頻用"IMA4" ,視頻用"H263"
處理:音頻,我們需要"JavaSound"來支持java1.3或者更高版本,SunAudio來支持Sun 的pre-1.3 JVMs。視頻,"AWT"就夠了。
創(chuàng)建的結果是我們得到了一個精簡的.jar文件――從普通1.9 MB的 jmf.jar 到我們定制的小于700K的jar包。
假定你已經編譯了兩個com.mac.invalidname.spmovie類,并把他們加到了定制的jar包里:
jar uf customized.jar com/mac/invalidname/spmovie/*.class
JMF的許可條例要求任何JMF的定制子集里都必須包括他的read-me文件。我已經在目錄misc里提供了這個文件,TinyPlayer可以找到它。
jar uf customized.jar misc/
為了實現(xiàn).jar的可雙擊,我們提供了一個manifest文件來告訴Java runtime 在雙擊或者使用-jar命令行參數(shù)調用的時候.jar里的哪個類包含可以調用的main()。Manifest還提供僅僅包括jar 自身的一個lasspath:
Main-Class: com.mac.invalidname.spmovie.TinyPlayer
Class-Path: .
用以下命令來加入manifest:
jar ufm customized.jar manifest-stub.txt
現(xiàn)在這個文件已經包含在jar播放電影所需要所有的代碼了。為了將來的使用,把它保存為spmovie-engine.jar或者任何類似的文件。
現(xiàn)在引擎已經設置好了,所需要的就只剩下媒體文件。我們可以從JMF 支持類型頁面可以看到,純java版本的JMF只支持有限集合的類型?赡芤曨l的最好選擇是H.263,它在很寬的比特率范圍里都表現(xiàn)的很好,但是對于很多老機器來說它顯得過于臃腫,你可以通過減小視頻或者保持一個較低的幀數(shù)率來解決。音頻沒有這么的約定俗成,但是我想IMA4:1表現(xiàn)的相當不錯。把你的媒體文件通過合適的編碼或者轉碼,拷貝到movie/themovie.mov或者movie/themovie.avi。如果需要你可以重命名.jar文件(我用的是spmovie.jar)然后通過下面的方式加入媒體文件:
jar u0f spmovie.jar media/
注意是"0"數(shù)字零,而不是字母0;表明我們不想壓縮這個目錄,因為媒體文件已經被壓縮過了。
我們得到的結果就是自播放電影――一個在雙擊的時候知道該運行那個類,提供所有信號分離,解碼,處理電影所需要的代碼和電影本身。作為示例,這里有個小型的自播放電影,它是我4個月兒子Keagan在玩耍的時候錄制的(使用了FreePlay Music免版稅的音樂)。
把這個概念擴展到applet是一件很簡單的事情,這樣我們可以讓媒體文件在所有支持的java的瀏覽器里播放。
可能有人會說我們解決錯了問題――是java虛擬機而不是媒體播放器在客戶端提供了支持。但是通過提供"一次創(chuàng)作,到處運行"的媒體文件,我們實現(xiàn)了java的初衷。