简体   繁体   English

如何从 JMF 中的类路径加载媒体资源

[英]How to load media resources from the classpath in JMF

I have a Java application that I want to turn into an executable jar.我有一个 Java 应用程序,我想把它变成一个可执行的 jar。 I am using JMF in this application, and I can't seem to get the sound files working right...我在这个应用程序中使用 JMF,我似乎无法让声音文件正常工作......

I create the jar using我使用创建罐子

jar cvfm jarname.jar manifest.txt *.class *.gif *.wav

So, all the sound files get put inside the jar, and in the code, I am creating the Players using所以,所有的声音文件都放在罐子里,在代码中,我正在使用创建播放器

Player player = Manager.createPlayer(ClassName.class.getResource("song1.wav"));

The jar is on my desktop, and when I attempt to run it, this exception occurs: jar 在我的桌面上,当我尝试运行它时,会发生此异常:

javax.media.NoPlayerException: Cannot find a Player for :jar:file:/C:/Users/Pojo/
Desktop/jarname.jar!/song1.wav

...It's not getting IOExceptions, so it seems to at least be finding the file itself all right. ...它没有得到 IOExceptions,所以它似乎至少可以找到文件本身。

Also, before I used the getResource, I used to have it like this:另外,在我使用 getResource 之前,我曾经这样使用它:

Player player = Manager.createPlayer(new File("song1.wav").toURL());

and it was playing fine, so I know nothing is wrong with the sound file itself.它播放得很好,所以我知道声音文件本身没有问题。

The reason I am trying to switch to this method instead of the File method is so that the sound files can be packaged inside the jar itself and not have to be its siblings in a directory.我尝试切换到此方法而不是 File 方法的原因是,声音文件可以打包在 jar 本身中,而不必是目录中的兄弟姐妹。

This is a far cry from production code, but this seems resolved any runtime exceptions (though it's not actually wired up to play anything yet):这与生产代码相去甚远,但这似乎解决了任何运行时异常(尽管它实际上还没有连接到播放任何东西):

import javax.media.Manager;
import javax.media.Player;
import javax.media.protocol.URLDataSource;

// ...

URL url = JmfTest.class.getResource("song1.wav");
System.out.println("url: " + url);
URLDataSource uds = new URLDataSource(url);
uds.connect();
Player player = Manager.createPlayer(uds);

New solution:新解决方案:

First, a custom DataSource class that returns a SourceStream that implements Seekable is needed:首先,需要一个返回实现SeekableSourceStream的自定义DataSource类:

package com.ziesemer.test;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.media.Duration;
import javax.media.MediaLocator;
import javax.media.Time;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.PullDataSource;
import javax.media.protocol.PullSourceStream;
import javax.media.protocol.Seekable;

/**
 * @author Mark A. Ziesemer
 *  <a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class JarDataSource extends PullDataSource{

    protected JarURLConnection conn;
    protected ContentDescriptor contentType;
    protected JarPullSourceStream[] sources;
    protected boolean connected;

    public JarDataSource(URL url) throws IOException{
        setLocator(new MediaLocator(url));
        connected = false;
    }

    @Override
    public PullSourceStream[] getStreams(){
        return sources;
    }

    @Override
    public void connect() throws IOException{
        conn = (JarURLConnection)getLocator().getURL().openConnection();
        conn.connect();
        connected = true;

        JarFile jf = conn.getJarFile();
        JarEntry je = jf.getJarEntry(conn.getEntryName());

        String mimeType = conn.getContentType();
        if(mimeType == null){
            mimeType = ContentDescriptor.CONTENT_UNKNOWN;
        }
        contentType = new ContentDescriptor(ContentDescriptor.mimeTypeToPackageName(mimeType));

        sources = new JarPullSourceStream[1];
        sources[0] = new JarPullSourceStream(jf, je, contentType);
    }

    @Override
    public String getContentType(){
        return contentType.getContentType();
    }

    @Override
    public void disconnect(){
        if(connected){
            try{
                sources[0].close();
            }catch(IOException e){
                e.printStackTrace();
            }
            connected = false;
        }
    }

    @Override
    public void start() throws IOException{
        // Nothing to do.
    }

    @Override
    public void stop() throws IOException{
        // Nothing to do.
    }

    @Override
    public Time getDuration(){
        return Duration.DURATION_UNKNOWN;
    }

    @Override
    public Object[] getControls(){
        return new Object[0];
    }

    @Override
    public Object getControl(String controlName){
        return null;
    }

    protected class JarPullSourceStream implements PullSourceStream, Seekable, Closeable{

        protected final JarFile jarFile;
        protected final JarEntry jarEntry;
        protected final ContentDescriptor type;

        protected InputStream stream;
        protected long position;

        public JarPullSourceStream(JarFile jarFile, JarEntry jarEntry, ContentDescriptor type) throws IOException{
            this.jarFile = jarFile;
            this.jarEntry = jarEntry;
            this.type = type;
            this.stream = jarFile.getInputStream(jarEntry);
        }

        @Override
        public ContentDescriptor getContentDescriptor(){
            return type;
        }

        @Override
        public long getContentLength(){
            return jarEntry.getSize();
        }

        @Override
        public boolean endOfStream(){
            return position < getContentLength();
        }

        @Override
        public Object[] getControls(){
            return new Object[0];
        }

        @Override
        public Object getControl(String controlType){
            return null;
        }

        @Override
        public boolean willReadBlock(){
            if(endOfStream()){
                return true;
            }
            try{
                return stream.available() == 0;
            }catch(IOException e){
                return true;
            }
        }

        @Override
        public int read(byte[] buffer, int offset, int length) throws IOException{
            int read = stream.read(buffer, offset, length);
            position += read;
            return read;
        }

        @Override
        public long seek(long where){
            try{
                if(where < position){
                    stream.close();
                    stream = jarFile.getInputStream(jarEntry);
                    position = 0;
                }
                long skip = where - position;
                while(skip > 0){
                    long skipped = stream.skip(skip);
                    skip -= skipped;
                    position += skipped;
                }
            }catch(IOException ioe){
                // Made a best effort.
                ioe.printStackTrace();
            }
            return position;
        }

        @Override
        public long tell(){
            return position;
        }

        @Override
        public boolean isRandomAccess(){
            return true;
        }

        @Override
        public void close() throws IOException{
            try{
                stream.close();
            }finally{
                jarFile.close();
            }
        }

    }

}

Then, the above custom data source is used to create a player, and a ControllerListener is added to cause the player to loop:然后,使用上述自定义数据源创建播放器,并添加一个ControllerListener ,使播放器循环播放:

package com.ziesemer.test;

import java.net.URL;

import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.EndOfMediaEvent;
import javax.media.Manager;
import javax.media.Player;
import javax.media.Time;

/**
 * @author Mark A. Ziesemer
 *  <a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class JmfTest{
    public static void main(String[] args) throws Exception{
        URL url = JmfTest.class.getResource("Test.wav");
        JarDataSource jds = new JarDataSource(url);
        jds.connect();
        final Player player = Manager.createPlayer(jds);

        player.addControllerListener(new ControllerListener(){
            @Override
            public void controllerUpdate(ControllerEvent ce){
                if(ce instanceof EndOfMediaEvent){
                    player.setMediaTime(new Time(0));
                    player.start();
                }
            }
        });
        player.start();
    }
}

Note that without the custom data source, JMF tries repeatedly to seek back to the beginning - but fails, and eventually gives up.请注意,如果没有自定义数据源,JMF 会反复尝试回到起点 - 但失败了,最终放弃了。 This can be seen from debugging the same ControllerListener , which will receive a several events for each attempt.这可以从调试相同的ControllerListener中看出,每次尝试都会收到几个事件。

Or, using the MediaPlayer approach to loop (that you mentioned on my previous answer):或者,使用MediaPlayer方法进行循环(您在我之前的回答中提到过):

package com.ziesemer.test;

import java.net.URL;

import javax.media.Manager;
import javax.media.Player;
import javax.media.bean.playerbean.MediaPlayer;

/**
 * @author Mark A. Ziesemer
 *  <a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class JmfTest{
    public static void main(String[] args) throws Exception{
        URL url = JmfTest.class.getResource("Test.wav");
        JarDataSource jds = new JarDataSource(url);
        jds.connect();
        final Player player = Manager.createPlayer(jds);

        MediaPlayer mp = new MediaPlayer();
        mp.setPlayer(player);
        mp.setPlaybackLoop(true);
        mp.start();
    }
}

Again, I would not consider this production-ready code (could use some more Javadocs and logging, etc.), but it is tested and working (Java 1.6), and should meet your needs nicely.同样,我不会考虑这种生产就绪代码(可以使用更多的 Javadocs 和日志记录等),但它已经过测试并且可以工作(Java 1.6),并且应该很好地满足您的需求。

Merry Christmas, and happy holidays!圣诞快乐,节日快乐!

Manager.createPlayer(this.getClass().getResource("/song1.wav"));

如果song1.wav位于应用程序运行时类路径上的 Jar 的根目录中,这将起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM