/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.android_scripting.facade.media;
import android.app.Service;
import android.media.MediaPlayer;
import android.net.Uri;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcDefault;
import com.googlecode.android_scripting.rpc.RpcParameter;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* This facade exposes basic mediaPlayer functionality.
*
* Usage Notes:
* mediaPlayerFacade maintains a list of media streams, identified by a user supplied tag. If the
* tag is null or blank, this tag defaults to "default"
* Basic operation is: mediaPlayOpen("file:///sdcard/MP3/sample.mp3","mytag",true)
* This will look for a media file at /sdcard/MP3/sample.mp3. Other urls should work. If the file
* exists and is playable, this will return a true otherwise it will return a false.
* If play=true, then the media file will play immediately, otherwise it will wait for a
* {@link #mediaPlayStart mediaPlayerStart} command.
* When done with the resource, use {@link #mediaPlayClose mediaPlayClose}
* You can get information about the loaded media with {@link #mediaPlayInfo mediaPlayInfo} This
* returns a map with the following elements:
*
* - "tag" - user supplied tag identifying this mediaPlayer.
*
- "loaded" - true if loaded, false if not. If false, no other elements are returned.
*
- "duration" - length of the media in milliseconds.
*
- "position" - current position of playback in milliseconds. Controlled by
* {@link #mediaPlaySeek mediaPlaySeek}
*
- "isplaying" - shows whether media is playing. Controlled by {@link #mediaPlayPause
* mediaPlayPause} and {@link #mediaPlayStart mediaPlayStart}
*
- "url" - the url used to open this media.
*
- "looping" - whether media will loop. Controlled by {@link #mediaPlaySetLooping
* mediaPlaySetLooping}
*
*
* You can use {@link #mediaPlayList mediaPlayList} to get a list of the loaded tags.
* {@link #mediaIsPlaying mediaIsPlaying} will return true if the media is playing.
* Events:
* A playing media will throw a "media" event on completion. NB: In remote mode, a media file
* will continue playing after the script has finished unless an explicit {@link #mediaPlayClose
* mediaPlayClose} event is called.
*
*/
public class MediaPlayerFacade extends RpcReceiver implements MediaPlayer.OnCompletionListener {
private final Service mService;
static private final Map mPlayers = new Hashtable();
static private final Map mUrls = new Hashtable();
private final EventFacade mEventFacade;
public MediaPlayerFacade(FacadeManager manager) {
super(manager);
mService = manager.getService();
mEventFacade = manager.getReceiver(EventFacade.class);
}
private String getDefault(String tag) {
return (tag == null || tag.equals("")) ? "default" : tag;
}
private MediaPlayer getPlayer(String tag) {
tag = getDefault(tag);
return mPlayers.get(tag);
}
private String getUrl(String tag) {
tag = getDefault(tag);
return mUrls.get(tag);
}
private void putMp(String tag, MediaPlayer player, String url) {
tag = getDefault(tag);
mPlayers.put(tag, player);
mUrls.put(tag, url);
}
private void removeMp(String tag) {
tag = getDefault(tag);
MediaPlayer player = mPlayers.get(tag);
if (player != null) {
player.stop();
player.release();
}
mPlayers.remove(tag);
mUrls.remove(tag);
}
@Rpc(description = "Open a media file", returns = "true if play successful")
public synchronized boolean mediaPlayOpen(@RpcParameter(name = "url",
description = "url of media resource")
String url, @RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag, @RpcParameter(name = "play", description = "start playing immediately")
@RpcDefault(value = "true")
Boolean play) {
removeMp(tag);
MediaPlayer player = getPlayer(tag);
player = MediaPlayer.create(mService, Uri.parse(url));
if (player != null) {
putMp(tag, player, url);
player.setOnCompletionListener(this);
if (play) {
player.start();
}
}
return player != null;
}
@Rpc(description = "pause playing media file", returns = "true if successful")
public synchronized boolean mediaPlayPause(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
if (player == null) {
return false;
}
player.pause();
return true;
}
@Rpc(description = "Start playing media file.", returns = "true if successful")
public synchronized boolean mediaPlayStart(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
if (player == null) {
return false;
}
player.start();
return mediaIsPlaying(tag);
}
@Rpc(description = "Stop playing media file.", returns = "true if successful")
public synchronized boolean mediaPlayStop(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
if (player == null) {
return false;
}
player.stop();
return !mediaIsPlaying(tag) && player.getCurrentPosition() == 0;
}
@Rpc(description = "Stop all players.")
public synchronized void mediaPlayStopAll() {
for (MediaPlayer p : mPlayers.values()) {
p.stop();
}
}
@Rpc(description = "Seek To Position", returns = "New Position (in ms)")
public synchronized int mediaPlaySeek(@RpcParameter(name = "msec",
description = "Position in millseconds")
Integer msec, @RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
if (player == null) {
return 0;
}
player.seekTo(msec);
return player.getCurrentPosition();
}
@Rpc(description = "Close media file", returns = "true if successful")
public synchronized boolean mediaPlayClose(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) throws Exception {
if (!mPlayers.containsKey(tag)) {
return false;
}
removeMp(tag);
return true;
}
@Rpc(description = "Checks if media file is playing.", returns = "true if playing")
public synchronized boolean mediaIsPlaying(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
return (player == null) ? false : player.isPlaying();
}
@Rpc(description = "Information on current media", returns = "Media Information")
public synchronized Map mediaPlayGetInfo(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
Map result = new HashMap();
MediaPlayer player = getPlayer(tag);
result.put("tag", getDefault(tag));
if (player == null) {
result.put("loaded", false);
} else {
result.put("loaded", true);
result.put("duration", player.getDuration());
result.put("position", player.getCurrentPosition());
result.put("isplaying", player.isPlaying());
result.put("url", getUrl(tag));
result.put("looping", player.isLooping());
}
return result;
}
@Rpc(description = "Lists currently loaded media", returns = "List of Media Tags")
public Set mediaPlayList() {
return mPlayers.keySet();
}
@Rpc(description = "Set Looping", returns = "True if successful")
public synchronized boolean mediaPlaySetLooping(@RpcParameter(name = "enabled")
@RpcDefault(value = "true")
Boolean enabled, @RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag) {
MediaPlayer player = getPlayer(tag);
if (player == null) {
return false;
}
player.setLooping(enabled);
return true;
}
@Rpc(description = "Checks if media file is playing.", returns = "true if playing")
public synchronized void mediaSetNext(
@RpcParameter(name = "tag", description = "string identifying resource")
@RpcDefault(value = "default")
String tag,
@RpcParameter(name = "next", description = "tag of the next track to play.")
String next) {
MediaPlayer player = getPlayer(tag);
MediaPlayer nPlayer = getPlayer(next);
if (player == null) {
throw new NullPointerException("Non-existent player tag " + tag);
}
if (nPlayer == null) {
throw new NullPointerException("Non-existent player tag " + next);
}
player.setNextMediaPlayer(nPlayer);
}
@Override
public synchronized void shutdown() {
for (String key : mPlayers.keySet()) {
MediaPlayer player = mPlayers.get(key);
if (player != null) {
player.stop();
player.release();
player = null;
}
}
mPlayers.clear();
mUrls.clear();
}
@Override
public void onCompletion(MediaPlayer player) {
String tag = getTag(player);
if (tag != null) {
Map data = new HashMap();
data.put("action", "complete");
data.put("tag", tag);
mEventFacade.postEvent("media", data);
}
}
private String getTag(MediaPlayer player) {
for (Entry m : mPlayers.entrySet()) {
if (m.getValue() == player) {
return m.getKey();
}
}
return null;
}
}