Cant read duration of .ogg

Hey guys,

I am having trouble reading the length of an .ogg file. Playback and other audiodata-reads work fine. I tested this with different mp3->ogg converters including audacity and the vlc-player regognizes the duration.

[java] OGGLoader loader = new OGGLoader();

AudioKey key = new AudioKey(file.getName(), true);

try{

AudioData musicData = (AudioData) loader.load(new AssetInfo(assetManager, key) {

public InputStream openStream() {

try{

return new FileInputStream(file);

}catch (FileNotFoundException ex){

return null;

}

}

});

musicData.getDuration(); <


always -1
}catch (IOException ex){
}[/java]
The same problem:
[java] AudioNode musicSource = new AudioNode(ar, manager, "test.ogg", true);
musicSource.getAudioData().getDuration(); <
always -1 [/java]
Edit: Also setting a time-offset doesnt work - the file is always being played form the beginning:
[java] musicSource.setTimeOffset(currentTime);
if (musicSource.getStatus() == Status.Playing){
ar.stopSource(musicSource);
ar.playSource(musicSource);
}[/java]

Streaming sources are limited. You cannot get their duration, you cannot loop them, and you cannot set their time offset.



To test this, you could convert it to non-streaming and I bet everything starts working… though it will load the whole thing in RAM.

Thanks, it works by loading into ram :slight_smile:

Argh I have to correct my statement. Loading into ram is absolutely horrible for performance. Will streaming be revised? Like this I am not able to create a simple audio-player without performance issues.

I’m just a user in this case, too… but you can’t know the length of a streamed file without reading it all… so I think it is unlikely that streamed AudioNodes will ever be able to tell you their length.



I do a mix of streamed and non-streamed ogg files without any performance issues. What are you seeing?

Ah I didnt know it wasnt possible at all. But how are regular audio-players handling this then?

When playing a music file ~3mb the application blocks for about 2 seconds. Memory-usage of the Application rises by almost 200mb per file.

That’s pretty crazy sounding. I’ve never seen anything like that, though my non-streamed oggs are not quite so big.



I think regular audio players probably seek ahead through the file first. JME has to contend with several layers of indirection in this case since all the audio engine gets from the asset manager is an InputStream.



…this is also why streamed audio cannot be replayed or looped in JME. The InputStream is done and there is no way to reset it.



I added long ambient loops to mythruna and went through great (well not so great) pains to loop my streamed media. Good times.





…by the way, are you running against a JME nightly build or an ā€˜older’ version?

Yeah i was quite ā€œamazedā€, too^^

I am running always newest nightly builds. However I now abandoned the timeslider and time-offset functionalities, using streams again. The looping I got to work by running a thread, checking wether the source is playing. I attached my whole JME-Musicplayer class, in case there is any need for it. The Playlist consists of the custom class MusicFile(Leaf), which can be contained by MusicCategory(Composite). I would also be happy to know, if I am violating any best practices, since I am new to jmonkey :slight_smile:

.

[java] public class JMEMusicPlayer implements IMusicPlayer {

protected AssetManager manager;

protected Listener listener;

protected AudioRenderer ar;

private boolean repeat = false;

private boolean shuffle = false;

private ArrayList<MusicFile> playList = new ArrayList<MusicFile>();

private int currentTrack = -1;

private boolean mute = false;

private float volume = 100.0f;

private AudioNode musicSource;

private boolean stop = true;

private ScheduledExecutorService playerThread = null;

private MusicManagerController mmc;

Random random;

public JMEMusicPlayer(){

ar = Main.getApp().getAudioRenderer();

manager = Main.getApp().getAssetManager();

listener = new Listener();

ar.setListener(listener);

}

public void run(){

if (playerThread == null){

playerThread = Executors.newSingleThreadScheduledExecutor();

playerThread.scheduleAtFixedRate(new Runnable() {

public void run() {

if (musicSource != null){

if ((musicSource.getStatus() == Status.Stopped) && !stop){

if (repeat){

play();

}

if (shuffle){

playList.remove(currentTrack);

currentTrack = random.nextInt(playList.size());

play();

}

if (playList.size()-1 > currentTrack){

next();

} else {

stop = true;

}

}

}

}

}, 0, 2, TimeUnit.SECONDS);

}

}

public void shutdown(){

if (playerThread != null){

playerThread.shutdown();

playerThread = null;

}

}

public void finalize() throws Throwable {

}

public void play(){

if (musicSource != null){

ar.stopSource(musicSource);

}

if (!playList.isEmpty()){

mmc.updateCurrentTrackLabel();

musicSource = new AudioNode(ar, manager, playList.get(currentTrack).getName(), true);

ar.playSource(musicSource);

stop = false;

}

}

public void stop(){

if (musicSource != null){

ar.stopSource(musicSource);

stop = true;

}

}

public void next(){

if (shuffle){

playList.remove(currentTrack);

currentTrack = random.nextInt(playList.size());

play();

} else if (playList.size()-1 > currentTrack){

currentTrack++;

play();

}

}

public boolean isRepeat(){

return repeat;

}

public void previous(){

if (shuffle){

playList.remove(currentTrack);

currentTrack = random.nextInt(playList.size());

play();

} else if (currentTrack > 0){

currentTrack–;

play();

}

}

/**

*

  • @param newVal

    */

    public void setRepeat(boolean newVal){

    repeat = newVal;

    }

    public boolean isShuffle(){

    return shuffle;

    }

    /**

    *
  • @param newVal

    */

    public void setShuffle(boolean newVal){

    shuffle = newVal;

    if (newVal){

    random = new Random();

    }

    }

    public boolean isMute(){

    return mute;

    }

    /**

    *
  • @param newVal

    */

    public void setMute(boolean newVal){

    if (newVal){

    listener.setVolume(0);

    mute = true;

    } else {

    listener.setVolume(volume / 100f);

    mute = false;

    }

    }

    public float getVolume(){

    return volume;

    }

    /**

    *
  • @param newVal

    */

    public void setVolume(float newVal){

    volume = newVal;

    if (!mute)

    listener.setVolume(volume / 100f);

    }

    public String getCurrentTrack(){

    if (!playList.isEmpty())

    return playList.get(currentTrack).getName();

    else

    return "";

    }

    /**

    *
  • @param playlist

    */

    public void addToPlayList(MusicComponent playList){

    if (playList instanceof MusicFile){

    this.playList.add((MusicFile)playList);

    } else {

    addMusicFilesToPlaylist((MusicCategory)playList);

    }

    if (musicSource != null){

    ar.stopSource(musicSource);

    }

    }

    /**

    *
  • @param playlist

    */

    public void setPlayList(MusicComponent playList){

    this.playList.clear();

    currentTrack = 0;

    if (playList instanceof MusicFile){

    this.playList.add((MusicFile)playList);

    } else {

    addMusicFilesToPlaylist((MusicCategory)playList);

    }

    if (musicSource != null){

    ar.stopSource(musicSource);

    }

    }

    private void addMusicFilesToPlaylist(MusicCategory cat){

    for (MusicComponent comp : cat.getMusicComponents()){

    if (comp instanceof MusicFile)

    playList.add((MusicFile)comp);

    else

    addMusicFilesToPlaylist((MusicCategory)comp);

    }

    }

    public void setMusicManagerController (MusicManagerController mmc) {

    this.mmc = mmc;

    }

    }[/java]

Adding seeking support to the AssetManager would be very difficult.



For files it is quite easy, but for HTTP, you need to implement your own system to do it.

For JAR/ZIP/Classpath based sources, its not possible without decomressing the whole file entry first.

All this means is that its impossible to add seeking support on the input stream level. It has to be done manually in the audio system.



This leaves only one option, read the whole file first and then cache it in an efficient format so that it can be seeked by the Vorbis decoder.

For WAV, this is not possible without reading the whole file. So if you have a 30 MB WAV file and you stream it, your memory usage will increase by 30 MB.



I’ll see what I can do in regards to OGG … Possibly you will have an option like ā€œenable cachingā€ which will allow looping, seeking, and determining length of streaming OGG files at the expense of having to load the entire (encoded vorbis) data into memory.

I added a new ā€œstream-cacheā€ feature to SVN. Use the new AudioKey constructor that takes two boolean arguments and pass ā€œtrueā€ for both of them. You can now access the duration parameter.



It takes about 200 ms to load the OGG file from disk in that mode, but if you’re streaming it from HTTP it takes much longer (10+ seconds) because the file must be downloaded entirely.

1 Like

Perfect, thank you! Didnt expect such an immidiate fix :smiley: Everything works fine with the stream cache and performance isn’t getting worse!

It would be nice if you could also add this feature to a new AudioNode constructor, so I can load files with only one line.