Как загрузить медиа-ресурсы из пути к классам в JMF

Итак, у меня есть Java-приложение, которое я хочу превратить в исполняемый jar-файл. Я использую JMF в этом приложении, и я не могу заставить звуковые файлы работать правильно...

Я создаю банку, используя

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

Итак, все звуковые файлы помещаются в банку, и в коде я создаю проигрыватели, используя

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

Банка находится на моем рабочем столе, и когда я пытаюсь ее запустить, возникает это исключение:

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

... Он не получает IOExceptions, поэтому, по крайней мере, кажется, что сам файл находится в порядке.

Кроме того, до того, как я использовал getResource, у меня было так:

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

и он проигрывался нормально, поэтому я знаю, что с самим звуковым файлом все в порядке.

Причина, по которой я пытаюсь переключиться на этот метод вместо метода File, заключается в том, что звуковые файлы могут быть упакованы внутри самой банки и не должны быть ее братьями и сестрами в каталоге.


person Pojo    schedule 29.11.2011    source источник
comment
Зачем использовать JMF для звука? javax.sound.sampled API был частью J2SE с версии 1.3.   -  person Andrew Thompson    schedule 29.11.2011
comment
@ Эндрю Томпсон В javax.sound.sampled есть что-нибудь еще, кроме клипа? Потому что я пытался использовать Clip, и он просто не работал для этих файлов, потому что они слишком большие, а Clip отказывается воспроизводить что-либо больше, чем около 1 МБ.   -  person Pojo    schedule 29.11.2011
comment
Клип отказывается воспроизводить что-либо больше 1 МБ A) Вам, вероятно, следует преобразовать их в формат MP3, чтобы они (как правило) были меньше, чем WAV. B) Если вы это сделаете, то для декодирования MP3 потребуется mp3plugin.jar JMF, но не весь JMF. C) Реализация Oracle Clip может обрабатывать не более 1 секунды 16-битного стереозвука с частотой 44,1 кГц, но есть два других способа обработки большого звука. 1) BigClip 2) Загрузите поток и воспроизведите его фрагмент по чанку.   -  person Andrew Thompson    schedule 29.11.2011
comment
@Andrew Thompson Weeeell ... Это относительно большой wav-файл, который нужно зациклить. Для этого я остановился на jmf в качестве решения. И все это работает, только почему-то нет... пока внутри баночки. Кроме того, вы сказали, что у BigClip есть проблемы с зацикливанием, верно?   -  person Pojo    schedule 29.11.2011


Ответы (3)


Новое решение:

Во-первых, необходим пользовательский класс DataSource, который возвращает SourceStream, реализующий Seekable:

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();
            }
        }

    }

}

Затем указанный выше пользовательский источник данных используется для создания проигрывателя, и добавляется 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();
    }
}

Обратите внимание, что без пользовательского источника данных JMF неоднократно пытается вернуться к началу, но терпит неудачу и в конечном итоге сдается. Это видно из отладки того же ControllerListener, который будет получать несколько событий за каждую попытку.

Или, используя подход 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();
    }
}

Опять же, я бы не стал рассматривать этот готовый к производству код (можно использовать еще несколько Javadocs, ведение журналов и т. д.), но он протестирован и работает (Java 1.6) и должен полностью соответствовать вашим потребностям.

Счастливого Рождества и счастливых праздников!

person ziesemer    schedule 24.12.2011
comment
Спасибо, мистер Z! Ты снова пришел ко мне! (На этот раз по-настоящему) ^_^ - person Pojo; 25.12.2011
comment
@ziesemer Возникло исключение Исключение в основном потоке java.lang.ClassCastException: sun.net.www.protocol.file.FileURLConnection не может быть приведено к java.net.JarURLConnection в com.JarDataSource.connect(JarDataSource.java:39) в com.JmfTest.main(JmfTest.java:20) - person Srivathsan; 02.08.2013
comment
@Srivathsan - Вы используете код как есть или с изменениями? Какая версия JDK? - person ziesemer; 02.08.2013
comment
@ziesemer Я использую тот же код, что вы описали выше. Моя версия JDK — 6. - person Srivathsan; 02.08.2013

Это далеко от производственного кода, но, похоже, он разрешил любые исключения во время выполнения (хотя на самом деле он еще не подключен для воспроизведения чего-либо):

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);
person ziesemer    schedule 29.11.2011
comment
Ах! Еще раз спасибо, мистер Циземер. Вы спасли мой проект. Я приветствую вас. ‹3 - person Pojo; 29.11.2011
comment
Сейчас подожди. Оказывается, звуковые файлы больше не зацикливаются даже при явном вызове setPlaybackLoop(true) в MediaPlayer. Они проигрываются один раз, а затем о них больше ничего не слышно. Они должны зацикливаться навсегда... - person Pojo; 29.11.2011
comment
Пожалуйста, включите дополнительный код, который воспроизводит и зацикливает звук, чтобы нам было с чем работать. - person ziesemer; 29.11.2011
comment
Что ж, я создаю игроков точно так же, как вы делали это в этом ответе; создайте объект URLDataSource, затем вызовите для него .connect(), затем назначьте его новому игроку с помощью Manager.createPlayer(URLDataSourceobject). Затем я делаю MediaPlayer player = new MediaPlayer(); player.setPlaybackLoop(true); - person Pojo; 29.11.2011
comment
... Затем, конечно, я делаю player.setPlayer(thePlayerObjectCreatedByTheManager); player.start(); В принципе, до того, как я реализовал ваш материал getResource, я просто получал URL-адреса от Files, и это нормально зацикливалось. Теперь я изменил код создания Player, чтобы использовать getResource, и теперь он больше не зацикливается. :/ - person Pojo; 29.11.2011

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

Это будет работать, если song1.wav находится в корне Jar, который находится на пути класса во время выполнения приложения.

person Andrew Thompson    schedule 29.11.2011
comment
это должно быть this.getClass()? Могу ли я не просто использовать имя класса .class ? - person Pojo; 29.11.2011
comment
Вы могли бы сделать это быстрее, чем добавлять комментарий и ждать, пока я спрошу Что произошло, когда вы попытались это сделать?. - person Andrew Thompson; 29.11.2011
comment
Тск. Глупые умные люди в наши дни... Что ж, я пробовал и то, и другое. Ни один из них не работал. - person Pojo; 29.11.2011