public interface Media
Known subtypesAbstractMedia, AsyncMedia
Media control interface allows for media playback, recording. To get an instance of this interface (implemented by the native port) see the MediaManager class.
The sample code below demonstrates simple video playback.
final Form hi = new Form("MediaPlayer", new BorderLayout());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_VIDEO_LIBRARY, s);
hi.getToolbar().addCommandToRightBar(new Command("", icon) {
@Override
public void actionPerformed(ActionEvent evt) {
Display.getInstance().openGallery((e) -> {
if(e != null && e.getSource() != null) {
String file = (String)e.getSource();
try {
Media video = MediaManager.createMedia(file, true);
hi.removeAll();
hi.add(BorderLayout.CENTER, new MediaPlayer(video));
hi.revalidate();
} catch(IOException err) {
Log.e(err);
}
}
}, Display.GALLERY_VIDEO);
}
});
hi.show();
The code below demonstrates capturing audio using the Capture API and playing back audio files using the Media API:
Form hi = new Form("Capture", BoxLayout.y());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_MIC, s);
FileSystemStorage fs = FileSystemStorage.getInstance();
String recordingsDir = fs.getAppHomePath() + "recordings/";
fs.mkdir(recordingsDir);
try {
for(String file : fs.listFiles(recordingsDir)) {
MultiButton mb = new MultiButton(file.substring(file.lastIndexOf("/") + 1));
mb.addActionListener((e) -> {
try {
Media m = MediaManager.createMedia(recordingsDir + file, false);
m.play();
} catch(IOException err) {
Log.e(err);
}
});
hi.add(mb);
}
hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
try {
String file = Capture.captureAudio();
if(file != null) {
SimpleDateFormat sd = new SimpleDateFormat("yyyy-MMM-dd-kk-mm");
String fileName =sd.format(new Date());
String filePath = recordingsDir + fileName;
Util.copy(fs.openInputStream(file), fs.openOutputStream(filePath));
MultiButton mb = new MultiButton(fileName);
mb.addActionListener((e) -> {
try {
Media m = MediaManager.createMedia(filePath, false);
m.play();
} catch(IOException err) {
Log.e(err);
}
});
hi.add(mb);
hi.revalidate();
}
} catch(IOException err) {
Log.e(err);
}
});
} catch(IOException err) {
Log.e(err);
}
hi.show();
The code below demonstrates capturing audio and playing back audio using the Media, MediaManager and MediaRecorderBuilder APIs, as alternative and more customizable approach than using the Capture API:
private static final EasyThread countTime = EasyThread.start("countTime");
public void start() {
if (current != null) {
current.show();
return;
}
Form hi = new Form("Recording audio", BoxLayout.y());
hi.add(new SpanLabel("Example of recording and playback audio using the Media, MediaManager and MediaRecorderBuilder APIs"));
hi.add(recordAudio((String filePath) -> {
ToastBar.showInfoMessage("Do something with the recorded audio file: " + filePath);
}));
hi.show();
}
public static Component recordAudio(OnComplete callback) {
try {
// mime types supported by Android: audio/amr, audio/aac, audio/mp4
// mime types supported by iOS: audio/mp4, audio/aac, audio/m4a
// mime type supported by Simulator: audio/wav
// more info: https://www.iana.org/assignments/media-types/media-types.xhtml
List availableMimetypes = Arrays.asList(MediaManager.getAvailableRecordingMimeTypes());
String mimetype;
if (availableMimetypes.contains("audio/aac")) {
// Android and iOS
mimetype = "audio/aac";
} else if (availableMimetypes.contains("audio/wav")) {
// Simulator
mimetype = "audio/wav";
} else {
// others
mimetype = availableMimetypes.get(0);
}
String fileName = "audioExample." + mimetype.substring(mimetype.indexOf("/") + 1);
String output = FileSystemStorage.getInstance().getAppHomePath() + "/" + fileName;
// https://tritondigitalcommunity.force.com/s/article/Choosing-Audio-Bitrate-Settings
MediaRecorderBuilder options = new MediaRecorderBuilder()
.mimeType(mimetype)
.path(output)
.bitRate(64000)
.samplingRate(44100);
Media[] microphone = {MediaManager.createMediaRecorder(options)};
Media[] speaker = {null};
Container recordingUI = new Container(BoxLayout.y());
Label time = new Label("0:00");
Button recordBtn = new Button("", FontImage.MATERIAL_FIBER_MANUAL_RECORD, "Button");
Button playBtn = new Button("", FontImage.MATERIAL_PLAY_ARROW, "Button");
Button stopBtn = new Button("", FontImage.MATERIAL_STOP, "Button");
Button sendBtn = new Button("Send");
sendBtn.setEnabled(false);
Container buttons = GridLayout.encloseIn(3, recordBtn, stopBtn, sendBtn);
recordingUI.addAll(FlowLayout.encloseCenter(time), FlowLayout.encloseCenter(buttons));
recordBtn.addActionListener(l -> {
try {
// every time we have to create a new instance of Media to make it working correctly (as reported in the Javadoc)
microphone[0] = MediaManager.createMediaRecorder(options);
if (speaker[0] != null && speaker[0].isPlaying()) {
return; // do nothing if the audio is currently recorded or played
}
recordBtn.setEnabled(false);
sendBtn.setEnabled(true);
Log.p("Audio recording started", Log.DEBUG);
if (buttons.contains(playBtn)) {
buttons.replace(playBtn, stopBtn, CommonTransitions.createEmpty());
buttons.revalidateWithAnimationSafety();
}
if (speaker[0] != null) {
speaker[0].pause();
}
microphone[0].play();
startWatch(time);
} catch (IOException ex) {
Log.p("ERROR recording audio", Log.ERROR);
Log.e(ex);
}
});
stopBtn.addActionListener(l -> {
if (!microphone[0].isPlaying() && (speaker[0] == null || !speaker[0].isPlaying())) {
return; // do nothing if the audio is NOT currently recorded or played
}
recordBtn.setEnabled(true);
sendBtn.setEnabled(true);
Log.p("Audio recording stopped");
if (microphone[0].isPlaying()) {
microphone[0].pause();
} else if (speaker[0] != null) {
speaker[0].pause();
} else {
return;
}
stopWatch(time);
if (buttons.contains(stopBtn)) {
buttons.replace(stopBtn, playBtn, CommonTransitions.createEmpty());
buttons.revalidateWithAnimationSafety();
}
if (FileSystemStorage.getInstance().exists(output)) {
Log.p("Audio saved to: " + output);
} else {
ToastBar.showErrorMessage("Error recording audio", 5000);
Log.p("ERROR SAVING AUDIO");
}
});
playBtn.addActionListener(l -> {
// every time we have to create a new instance of Media to make it working correctly (as reported in the Javadoc)
if (microphone[0].isPlaying() || (speaker[0] != null && speaker[0].isPlaying())) {
return; // do nothing if the audio is currently recorded or played
}
recordBtn.setEnabled(false);
sendBtn.setEnabled(true);
if (buttons.contains(playBtn)) {
buttons.replace(playBtn, stopBtn, CommonTransitions.createEmpty());
buttons.revalidateWithAnimationSafety();
}
if (FileSystemStorage.getInstance().exists(output)) {
try {
speaker[0] = MediaManager.createMedia(output, false, () -> {
// callback on completation
recordBtn.setEnabled(true);
if (speaker[0].isPlaying()) {
speaker[0].pause();
}
stopWatch(time);
if (buttons.contains(stopBtn)) {
buttons.replace(stopBtn, playBtn, CommonTransitions.createEmpty());
buttons.revalidateWithAnimationSafety();
}
});
speaker[0].play();
startWatch(time);
} catch (IOException ex) {
Log.p("ERROR playing audio", Log.ERROR);
Log.e(ex);
}
}
});
sendBtn.addActionListener(l -> {
if (microphone[0].isPlaying()) {
microphone[0].pause();
}
if (speaker[0] != null && speaker[0].isPlaying()) {
speaker[0].pause();
}
if (buttons.contains(stopBtn)) {
buttons.replace(stopBtn, playBtn, CommonTransitions.createEmpty());
buttons.revalidateWithAnimationSafety();
}
stopWatch(time);
recordBtn.setEnabled(true);
callback.completed(output);
});
return FlowLayout.encloseCenter(recordingUI);
} catch (IOException ex) {
Log.p("ERROR recording audio", Log.ERROR);
Log.e(ex);
return new Label("Error recording audio");
}
}
private static void startWatch(Label label) {
label.putClientProperty("stopTime", Boolean.FALSE);
countTime.run(() -> {
long startTime = System.currentTimeMillis();
while (label.getClientProperty("stopTime") == Boolean.FALSE) {
// the sleep is every 200ms instead of 1000ms to make the app more reactive when stop is tapped
Util.sleep(200);
int seconds = (int) ((System.currentTimeMillis() - startTime) / 1000);
String min = (seconds / 60) + "";
String sec = (seconds % 60) + "";
if (sec.length() == 1) {
sec = "0" + sec;
}
String newTime = min + ":" + sec;
if (!label.getText().equals(newTime)) {
CN.callSerially(() -> {
label.setText(newTime);
if (label.getParent() != null) {
label.getParent().revalidateWithAnimationSafety();
}
});
}
}
});
}
private static void stopWatch(Label label) {
label.putClientProperty("stopTime", Boolean.TRUE);
}
See also
Fields
public static final String VARIABLE_BACKGROUND_TITLE = "bgTitle" | Write only variable that can be used with setVariable to pass a title for the native layer so the currently playing media title will be displayed in the lock screen where applicable |
public static final String VARIABLE_BACKGROUND_ARTIST = "bgArtist" | Write only variable that can be used with setVariable to pass the artist name to the native layer so the currently playing media title will be displayed in the lock screen where applicable |
public static final String VARIABLE_BACKGROUND_DURATION = "bgDuration" | Write only variable that can be used with setVariable to pass the duration for the media as a Long object to the native layer so the currently playing media title will be displayed in the lock screen where applicable |
public static final String VARIABLE_BACKGROUND_ALBUM_COVER = "bgCover" | Write only variable that can be used with setVariable to pass the album cover Image to the native layer so the currently playing media title will be displayed in the lock screen where applicable |
public static final String VARIABLE_BACKGROUND_POSITION = "bgPosition" | Write only variable that can be used with setVariable to pass the position in the media (Long object) to the native layer so the currently playing media title will be displayed in the lock screen where applicable |
public static final String VARIABLE_BACKGROUND_SUPPORTED = "bgInfoSupported" | Read only variable that can be used with getVariable to query whether the native layer supports displaying the currently playing media information in the lock screen. |
public static final String VARIABLE_NATIVE_CONTRLOLS_EMBEDDED = "nativeControlsVisible" | Write-only variable that can be used with getVariable() to set whether this video should include embedded native controls. |
public static final String VARIABLE_ANDROID_SEEK_PREVIEW_WORKAROUND = "androidSeekPreviewWorkaround" | Optional boolean flag to enable an Android-specific workaround for flaky black-frame previews after setTime(int) on paused video. |
Methods
public abstract void play() | Starts playing or recording the media file |
public abstract void pause() | Pauses (actually stops) the playback or the recording of the media file |
public abstract void prepare() | Optional call that allows the caller to prepare the upcoming media player. |
public abstract void cleanup() | Stops the audio playback and cleans up the resources related to it immediately. |
public abstract int getTime() | Returns the time in milliseconds in the audio file or -1 if not known |
public abstract void setTime(int time) | Sets the position in the audio file or doesn’t effect if not supported |
public abstract int getDuration() | Returns the length in milliseconds of the audio file or -1 if not known |
public abstract int getVolume() | Returns the media playback volume in percentage |
public abstract void setVolume(int vol) | Sets the media playback volume in percentage |
public abstract boolean isPlaying() | Returns true if the media is currently playing or recording |
public abstract Component getVideoComponent() | Gets the VideoComponent of this Video. |
public abstract boolean isVideo() | This method returns true if this is a Video Media |
public abstract boolean isFullScreen() | This method returns true if this video is in full screen mode. |
public abstract void setFullScreen(boolean fullScreen) | Sets the Media to be displayed full screen, make sure the getVideoComponent() is called on the Video Component is added to the current Form |
public abstract boolean isNativePlayerMode() | Returns true if this Video Media is in Native player mode. |
public abstract void setNativePlayerMode(boolean nativePlayer) | By calling this the Media (if it’s a Video) will be played full screen on the native Player of the device. |
public abstract void setVariable(String key, Object value) | Allows for platform specific enhancements for media playback |
public abstract Object getVariable(String key) | Allows querying platform specific information from the media object |
Field details
VARIABLE_BACKGROUND_TITLE
public static final String VARIABLE_BACKGROUND_TITLE = "bgTitle"VARIABLE_BACKGROUND_ARTIST
public static final String VARIABLE_BACKGROUND_ARTIST = "bgArtist"VARIABLE_BACKGROUND_DURATION
public static final String VARIABLE_BACKGROUND_DURATION = "bgDuration"VARIABLE_BACKGROUND_ALBUM_COVER
public static final String VARIABLE_BACKGROUND_ALBUM_COVER = "bgCover"VARIABLE_BACKGROUND_POSITION
public static final String VARIABLE_BACKGROUND_POSITION = "bgPosition"VARIABLE_BACKGROUND_SUPPORTED
public static final String VARIABLE_BACKGROUND_SUPPORTED = "bgInfoSupported"VARIABLE_NATIVE_CONTRLOLS_EMBEDDED
public static final String VARIABLE_NATIVE_CONTRLOLS_EMBEDDED = "nativeControlsVisible"VARIABLE_ANDROID_SEEK_PREVIEW_WORKAROUND
public static final String VARIABLE_ANDROID_SEEK_PREVIEW_WORKAROUND = "androidSeekPreviewWorkaround"Optional boolean flag to enable an Android-specific workaround for flaky
black-frame previews after setTime(int) on paused video.
The default is false. When enabled, Android may perform an additional
internal refresh seek to force a frame redraw without changing behaviour
on other platforms.
Method details
play
public abstract void play()pause
public abstract void pause()prepare
public abstract void prepare()Optional call that allows the caller to prepare the upcoming media player. This is useful when streaming multiple streams one after another.
Note: On some platforms (iOS), the poster frame and native embedded controls will not appear for the video until you call this method, otherwise.
cleanup
public abstract void cleanup()getTime
public abstract int getTime()Returns
setTime
public abstract void setTime(int time)Parameters
timeint- in milliseconds
getDuration
public abstract int getDuration()Returns
getVolume
public abstract int getVolume()Returns
setVolume
public abstract void setVolume(int vol)Parameters
volint- the volume for media playback
isPlaying
public abstract boolean isPlaying()Returns
getVideoComponent
public abstract Component getVideoComponent()Returns
isVideo
public abstract boolean isVideo()Returns
isFullScreen
public abstract boolean isFullScreen()Returns
setFullScreen
public abstract void setFullScreen(boolean fullScreen)isNativePlayerMode
public abstract boolean isNativePlayerMode()Returns
setNativePlayerMode
public abstract void setNativePlayerMode(boolean nativePlayer)setVariable
public abstract void setVariable(String key, Object value)Parameters
keyString- the key to set to the media that is platform specific
valueObject- the value
getVariable
public abstract Object getVariable(String key)Parameters
keyString- the key query