/*
* Copyright (c) 2009 Andres Colubri
* Copyright (c) 2007 Wayne Meissner
*
* This file is part of gstreamer-java.
*
* This code is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License version 3 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gstreamer.elements;
import java.awt.Dimension;
import java.io.File;
import java.net.URI;
import org.gstreamer.Element;
import org.gstreamer.ElementFactory;
import org.gstreamer.Format;
import org.gstreamer.Fraction;
import org.gstreamer.Pad;
import org.gstreamer.Pipeline;
import org.gstreamer.Video;
import org.gstreamer.lowlevel.GstAPI.GstCallback;
/**
* <p>
* Playbin2 provides a stand-alone everything-in-one abstraction for an
* audio and/or video player.
* </p>
* <p>
* At this stage, playbin2 is considered UNSTABLE. The API provided in the
* signals and properties may yet change in the near future. When playbin2
* is stable, it will probably replace <span class="type">playbin</span>
* </p>
* <p>
* It can handle both audio and video files and features
* </p>
* <ul>
* <li>
* automatic file type recognition and based on that automatic
* selection and usage of the right audio/video/subtitle demuxers/decoders
* </li>
* <li>
* visualisations for audio files
* </li>
* <li>
* subtitle support for video files. Subtitles can be store in external
* files.
* </li>
* <li>
* stream selection between different video/audio/subtitles streams
* </li>
* <li>
* meta info (tag) extraction
* </li>
* <li>
* easy access to the last video frame
* </li>
* <li>
* buffering when playing streams over a network
* </li>
* <li>
* volume control with mute option
* </li>
* </ul>
* <p>
* Playbin2 is a {@link org.gstreamer.Pipeline}. It will notify the application of everything
* that's happening (errors, end of stream, tags found, state changes, etc.)
* by posting messages on its {@link org.gstreamer.Bus}. The application needs to watch the
* bus.
*
* <p>
* Playback can be initiated by setting the PlayBin2 to PLAYING state using
* {@link #setState setState} or {@link #play play}. Note that the state change will take place in
* the background in a separate thread, when the function returns playback
* is probably not happening yet and any errors might not have occured yet.
* Applications using playbin should ideally be written to deal with things
* completely asynchroneous.
* </p>
*
* <p>
* When playback has finished (an EOS message has been received on the bus)
* or an error has occured (an ERROR message has been received on the bus) or
* the user wants to play a different track, playbin should be set back to
* READY or NULL state, then the input file/URI should be set to the new
* location and then playbin be set to PLAYING state again.
* </p>
*
* <p>
* Seeking can be done using {@link #seek seek} on the playbin2 element.
* Again, the seek will not be executed instantaneously, but will be done in a
* background thread. When the seek call returns the seek will most likely still
* be in process. An application may wait for the seek to finish (or fail) using
* {@link #getState(long)} with -1 as the timeout, but this will block the user
* interface and is not recommended at all.
*
* <p>
* Applications may query the current position and duration of the stream
* via {@link #queryPosition} and {@link #queryDuration} and
* setting the format passed to {@link Format#TIME}. If the query was successful,
* the duration or position will have been returned in units of nanoseconds.
* </p>
*/
public class PlayBin2 extends Pipeline {
public static final String GST_NAME = "playbin2";
public static final String GTYPE_NAME = "GstPlayBin2";
/**
* Creates a new PlayBin2.
*
* @param name The name used to identify this pipeline.
*/
public PlayBin2(String name) {
this(makeRawElement(GST_NAME, name));
}
/**
* Creates a new PlayBin2.
*
* @param name The name used to identify this pipeline.
* @param uri The URI of the media file to load.
*/
public PlayBin2(String name, URI uri) {
this(name);
setURI(uri);
}
/**
* Creates a new PlayBin2 proxy.
*
* @param init proxy initialization args
*
*/
public PlayBin2(Initializer init) {
super(init);
}
/*private static String slashify(String path, boolean isDirectory) {
String p = path;
if (File.separatorChar != '/')
p = p.replace(File.separatorChar, '/');
if (!p.startsWith("/"))
p = "/" + p;
if (!p.endsWith("/") && isDirectory)
p = p + "/";
return p;
}*/
/**
* Sets the media file to play.
*
* @param file The {@link java.io.File} to play.
*/
public void setInputFile(File file) {
setURI(file.toURI());
}
/**
* Sets the media URI to play.
*
* @param uri The {@link java.net.URI} to play.
*/
public void setURI(URI uri) {
set("uri", uri);
}
/**
* Sets the audio output Element.
* <p> To disable audio output, call this method with a <tt>null</tt> argument.
*
* @param element The element to use for audio output.
*/
public void setAudioSink(Element element) {
setElement("audio-sink", element);
}
/**
* Sets the video output Element.
* <p> To disable video output, call this method with a <tt>null</tt> argument.
*
* @param element The element to use for video output.
*/
public void setVideoSink(Element element) {
setElement("video-sink", element);
}
/**
* Sets the visualization output Element.
*
* @param element The element to use for visualization.
*/
public void setVisualization(Element element) {
setElement("vis-plugin", element);
}
/**
* Sets an output {@link Element} on the PlayBin.
*
* @param key The name of the output to change.
* @param element The Element to set as the output.
*/
private void setElement(String key, Element element) {
if (element == null) {
element = ElementFactory.make("fakesink", "fake-" + key);
}
set(key, element);
}
/**
* Set the volume for the PlayBin.
*
* @param percent Percentage (between 0 and 100) to set the volume to.
*/
public void setVolumePercent(int percent) {
setVolume(Math.max(Math.min((double) percent, 100d), 0d) / 100d);
}
/**
* Get the current volume.
* @return The current volume as a percentage between 0 and 100 of the max volume.
*/
public int getVolumePercent() {
return (int) ((getVolume() * 100d) + 0.5);
}
/**
* Sets the audio playback volume.
*
* @param volume value between 0.0 and 1.0 with 1.0 being full volume.
*/
public void setVolume(double volume) {
set("volume", Math.max(Math.min(volume, 1d), 0d));
}
/**
* Gets the current volume.
* @return The current volume as a percentage between 0 and 100 of the max volume.
*/
public double getVolume() {
return ((Number) get("volume")).doubleValue();
}
/**
* Retrieves the framerate from the caps of the video sink's pad.
*
* @return frame rate (frames per second), or 0 if the framerate is not
* available
*/
public double getVideoSinkFrameRate() {
for (Element sink : getSinks()) {
for (Pad pad : sink.getPads()) {
Fraction frameRate = Video.getVideoFrameRate(pad);
if (frameRate != null) {
return frameRate.toDouble();
}
}
}
return 0;
}
/**
* Retrieves the width and height of the video frames configured in the caps
* of the video sink's pad.
*
* @return dimensions of the video frames, or null if the video frame size is
* not available
*/
public Dimension getVideoSize() {
for (Element sink : getSinks()) {
for (Pad pad : sink.getPads()) {
Dimension size = Video.getVideoSize(pad);
if (size != null) {
return size;
}
}
}
return null;
}
/**
* Signal emitted when the current uri is about to finish. You can set
* the uri and suburi to make sure that playback continues.
*/
public static interface ABOUT_TO_FINISH {
/**
*/
public void aboutToFinish(PlayBin2 element);
}
/**
* Adds a listener for the <code>about-to-finish</code> signal
*/
public void connect(final ABOUT_TO_FINISH listener) {
connect(ABOUT_TO_FINISH.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem) {
listener.aboutToFinish(elem);
}
});
}
/**
* Removes a listener for the <code>about-to-finish</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(ABOUT_TO_FINISH listener) {
disconnect(ABOUT_TO_FINISH.class, listener);
}
/**
* Signal is emitted whenever the number or order of the video streams has changed.
* The application will most likely want to select a new video stream.
*/
public static interface VIDEO_CHANGED {
/**
*/
public void videoChanged(PlayBin2 element);
}
/**
* Adds a listener for the <code>video-changed</code> signal
*/
public void connect(final VIDEO_CHANGED listener) {
connect(VIDEO_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem) {
listener.videoChanged(elem);
}
});
}
/**
* Removes a listener for the <code>video-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(VIDEO_CHANGED listener) {
disconnect(VIDEO_CHANGED.class, listener);
}
/**
* Signal is emitted whenever the number or order of the audio streams has changed.
* The application will most likely want to select a new audio stream.
*/
public static interface AUDIO_CHANGED {
/**
*/
public void audioChanged(PlayBin2 element);
}
/**
* Adds a listener for the <code>audio-changed</code> signal
*/
public void connect(final AUDIO_CHANGED listener) {
connect(AUDIO_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem) {
listener.audioChanged(elem);
}
});
}
/**
* Removes a listener for the <code>audio-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(AUDIO_CHANGED listener) {
disconnect(AUDIO_CHANGED.class, listener);
}
/**
* Signal is emitted whenever the number or order of the audio streams has changed.
* The application will most likely want to select a new audio stream.
*/
public static interface TEXT_CHANGED {
/**
*/
public void textChanged(PlayBin2 element);
}
/**
* Adds a listener for the <code>audio-changed</code> signal
*/
public void connect(final TEXT_CHANGED listener) {
connect(TEXT_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem) {
listener.textChanged(elem);
}
});
}
/**
* Removes a listener for the <code>text-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(TEXT_CHANGED listener) {
disconnect(TEXT_CHANGED.class, listener);
}
/**
* Signal is emitted whenever the tags of a video stream have changed.
* The application will most likely want to get the new tags.
*/
public static interface VIDEO_TAGS_CHANGED {
/**
* @param stream stream index with changed tags
*/
public void videoTagsChanged(PlayBin2 element, int stream);
}
/**
* Adds a listener for the <code>video-tags-changed</code> signal
*/
public void connect(final VIDEO_TAGS_CHANGED listener) {
connect(VIDEO_TAGS_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem, int stream) {
listener.videoTagsChanged(elem, stream);
}
});
}
/**
* Removes a listener for the <code>video-tags-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(VIDEO_TAGS_CHANGED listener) {
disconnect(VIDEO_TAGS_CHANGED.class, listener);
}
/**
* Signal is emitted whenever the tags of an audio stream have changed.
* The application will most likely want to get the new tags.
*/
public static interface AUDIO_TAGS_CHANGED {
/**
* @param stream stream index with changed tags
*/
public void audioTagsChanged(PlayBin2 element, int stream);
}
/**
* Adds a listener for the <code>audio-tags-changed</code> signal
*/
public void connect(final AUDIO_TAGS_CHANGED listener) {
connect(AUDIO_TAGS_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem, int stream) {
listener.audioTagsChanged(elem, stream);
}
});
}
/**
* Removes a listener for the <code>audio-tags-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(AUDIO_TAGS_CHANGED listener) {
disconnect(AUDIO_TAGS_CHANGED.class, listener);
}
/**
* Signal is emitted whenever the tags of a text stream have changed.
* The application will most likely want to get the new tags.
*/
public static interface TEXT_TAGS_CHANGED {
/**
* @param stream stream index with changed tags
*/
public void textTagsChanged(PlayBin2 element, int stream);
}
/**
* Adds a listener for the <code>audio-tags-changed</code> signal
*/
public void connect(final TEXT_TAGS_CHANGED listener) {
connect(TEXT_TAGS_CHANGED.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem, int stream) {
listener.textTagsChanged(elem, stream);
}
});
}
/**
* Removes a listener for the <code>text-tags-changed</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(TEXT_TAGS_CHANGED listener) {
disconnect(TEXT_TAGS_CHANGED.class, listener);
}
/**
* This signal is emitted after the source element has been created,
* so it can be configured by setting additional properties (e.g.
* set a proxy server for an http source, or set the device and read
* speed for an audio cd source).
*
* This is functionally equivalent to connecting to the notify::source
* signal, but more convenient.
*
* This signal is usually emitted from the context of a GStreamer streaming thread.
*/
public static interface SOURCE_SETUP {
/**
* @param source source element
*/
public void sourceSetup(PlayBin2 element, Element source);
}
/**
* Adds a listener for the <code>source-setup</code> signal
*/
public void connect(final SOURCE_SETUP listener) {
connect(SOURCE_SETUP.class, listener, new GstCallback() {
@SuppressWarnings("unused")
public void callback(PlayBin2 elem, Element source) {
listener.sourceSetup(elem, source);
}
});
}
/**
* Removes a listener for the <code>source-setup</code> signal
*
* @param listener The listener that was previously added.
*/
public void disconnect(SOURCE_SETUP listener) {
disconnect(SOURCE_SETUP.class, listener);
}
}