1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting.facade.media;
18 
19 import android.app.Service;
20 import android.media.MediaPlayer;
21 import android.net.Uri;
22 
23 import com.googlecode.android_scripting.facade.EventFacade;
24 import com.googlecode.android_scripting.facade.FacadeManager;
25 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
26 import com.googlecode.android_scripting.rpc.Rpc;
27 import com.googlecode.android_scripting.rpc.RpcDefault;
28 import com.googlecode.android_scripting.rpc.RpcParameter;
29 
30 import java.util.HashMap;
31 import java.util.Hashtable;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.Map.Entry;
35 
36 /**
37  * This facade exposes basic mediaPlayer functionality. <br>
38  * <br>
39  * <b>Usage Notes:</b><br>
40  * mediaPlayerFacade maintains a list of media streams, identified by a user supplied tag. If the
41  * tag is null or blank, this tag defaults to "default"<br>
42  * Basic operation is: mediaPlayOpen("file:///sdcard/MP3/sample.mp3","mytag",true)<br>
43  * This will look for a media file at /sdcard/MP3/sample.mp3. Other urls should work. If the file
44  * exists and is playable, this will return a true otherwise it will return a false. <br>
45  * If play=true, then the media file will play immediately, otherwise it will wait for a
46  * {@link #mediaPlayStart mediaPlayerStart} command. <br>
47  * When done with the resource, use {@link #mediaPlayClose mediaPlayClose} <br>
48  * You can get information about the loaded media with {@link #mediaPlayInfo mediaPlayInfo} This
49  * returns a map with the following elements:
50  * <ul>
51  * <li>"tag" - user supplied tag identifying this mediaPlayer.
52  * <li>"loaded" - true if loaded, false if not. If false, no other elements are returned.
53  * <li>"duration" - length of the media in milliseconds.
54  * <li>"position" - current position of playback in milliseconds. Controlled by
55  * {@link #mediaPlaySeek mediaPlaySeek}
56  * <li>"isplaying" - shows whether media is playing. Controlled by {@link #mediaPlayPause
57  * mediaPlayPause} and {@link #mediaPlayStart mediaPlayStart}
58  * <li>"url" - the url used to open this media.
59  * <li>"looping" - whether media will loop. Controlled by {@link #mediaPlaySetLooping
60  * mediaPlaySetLooping}
61  * </ul>
62  * <br>
63  * You can use {@link #mediaPlayList mediaPlayList} to get a list of the loaded tags. <br>
64  * {@link #mediaIsPlaying mediaIsPlaying} will return true if the media is playing.<br>
65  * <b>Events:</b><br>
66  * A playing media will throw a <b>"media"</b> event on completion. NB: In remote mode, a media file
67  * will continue playing after the script has finished unless an explicit {@link #mediaPlayClose
68  * mediaPlayClose} event is called.
69  *
70  * @author Robbie Matthews (rjmatthews62@gmail.com)
71  */
72 
73 public class MediaPlayerFacade extends RpcReceiver implements MediaPlayer.OnCompletionListener {
74 
75     private final Service mService;
76     static private final Map<String, MediaPlayer> mPlayers = new Hashtable<String, MediaPlayer>();
77     static private final Map<String, String> mUrls = new Hashtable<String, String>();
78 
79     private final EventFacade mEventFacade;
80 
MediaPlayerFacade(FacadeManager manager)81     public MediaPlayerFacade(FacadeManager manager) {
82         super(manager);
83         mService = manager.getService();
84         mEventFacade = manager.getReceiver(EventFacade.class);
85     }
86 
getDefault(String tag)87     private String getDefault(String tag) {
88         return (tag == null || tag.equals("")) ? "default" : tag;
89     }
90 
getPlayer(String tag)91     private MediaPlayer getPlayer(String tag) {
92         tag = getDefault(tag);
93         return mPlayers.get(tag);
94     }
95 
getUrl(String tag)96     private String getUrl(String tag) {
97         tag = getDefault(tag);
98         return mUrls.get(tag);
99     }
100 
putMp(String tag, MediaPlayer player, String url)101     private void putMp(String tag, MediaPlayer player, String url) {
102         tag = getDefault(tag);
103         mPlayers.put(tag, player);
104         mUrls.put(tag, url);
105     }
106 
removeMp(String tag)107     private void removeMp(String tag) {
108         tag = getDefault(tag);
109         MediaPlayer player = mPlayers.get(tag);
110         if (player != null) {
111             player.stop();
112             player.release();
113         }
114         mPlayers.remove(tag);
115         mUrls.remove(tag);
116     }
117 
118     @Rpc(description = "Open a media file", returns = "true if play successful")
mediaPlayOpen(@pcParametername = "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)119     public synchronized boolean mediaPlayOpen(@RpcParameter(name = "url",
120                                                             description = "url of media resource")
121     String url, @RpcParameter(name = "tag", description = "string identifying resource")
122     @RpcDefault(value = "default")
123     String tag, @RpcParameter(name = "play", description = "start playing immediately")
124     @RpcDefault(value = "true")
125     Boolean play) {
126         removeMp(tag);
127         MediaPlayer player = getPlayer(tag);
128         player = MediaPlayer.create(mService, Uri.parse(url));
129         if (player != null) {
130             putMp(tag, player, url);
131             player.setOnCompletionListener(this);
132             if (play) {
133                 player.start();
134             }
135         }
136         return player != null;
137     }
138 
139     @Rpc(description = "pause playing media file", returns = "true if successful")
mediaPlayPause( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)140     public synchronized boolean mediaPlayPause(
141             @RpcParameter(name = "tag", description = "string identifying resource")
142             @RpcDefault(value = "default")
143             String tag) {
144         MediaPlayer player = getPlayer(tag);
145         if (player == null) {
146             return false;
147         }
148         player.pause();
149         return true;
150     }
151 
152     @Rpc(description = "Start playing media file.", returns = "true if successful")
mediaPlayStart( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)153     public synchronized boolean mediaPlayStart(
154             @RpcParameter(name = "tag", description = "string identifying resource")
155             @RpcDefault(value = "default")
156             String tag) {
157         MediaPlayer player = getPlayer(tag);
158         if (player == null) {
159             return false;
160         }
161         player.start();
162         return mediaIsPlaying(tag);
163     }
164 
165     @Rpc(description = "Stop playing media file.", returns = "true if successful")
mediaPlayStop( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)166     public synchronized boolean mediaPlayStop(
167             @RpcParameter(name = "tag", description = "string identifying resource")
168             @RpcDefault(value = "default")
169             String tag) {
170         MediaPlayer player = getPlayer(tag);
171         if (player == null) {
172             return false;
173         }
174         player.stop();
175         return !mediaIsPlaying(tag) && player.getCurrentPosition() == 0;
176     }
177 
178     @Rpc(description = "Stop all players.")
mediaPlayStopAll()179     public synchronized void mediaPlayStopAll() {
180         for (MediaPlayer p : mPlayers.values()) {
181             p.stop();
182         }
183     }
184 
185     @Rpc(description = "Seek To Position", returns = "New Position (in ms)")
mediaPlaySeek(@pcParametername = "msec", description = "Position in millseconds") Integer msec, @RpcParameter(name = "tag", description = "string identifying resource") @RpcDefault(value = "default") String tag)186     public synchronized int mediaPlaySeek(@RpcParameter(name = "msec",
187                                                         description = "Position in millseconds")
188     Integer msec, @RpcParameter(name = "tag", description = "string identifying resource")
189     @RpcDefault(value = "default")
190     String tag) {
191         MediaPlayer player = getPlayer(tag);
192         if (player == null) {
193             return 0;
194         }
195         player.seekTo(msec);
196         return player.getCurrentPosition();
197     }
198 
199     @Rpc(description = "Close media file", returns = "true if successful")
mediaPlayClose( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)200     public synchronized boolean mediaPlayClose(
201             @RpcParameter(name = "tag", description = "string identifying resource")
202             @RpcDefault(value = "default")
203             String tag) throws Exception {
204         if (!mPlayers.containsKey(tag)) {
205             return false;
206         }
207         removeMp(tag);
208         return true;
209     }
210 
211     @Rpc(description = "Checks if media file is playing.", returns = "true if playing")
mediaIsPlaying( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)212     public synchronized boolean mediaIsPlaying(
213             @RpcParameter(name = "tag", description = "string identifying resource")
214             @RpcDefault(value = "default")
215             String tag) {
216         MediaPlayer player = getPlayer(tag);
217         return (player == null) ? false : player.isPlaying();
218     }
219 
220     @Rpc(description = "Information on current media", returns = "Media Information")
mediaPlayGetInfo( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag)221     public synchronized Map<String, Object> mediaPlayGetInfo(
222             @RpcParameter(name = "tag", description = "string identifying resource")
223             @RpcDefault(value = "default")
224             String tag) {
225         Map<String, Object> result = new HashMap<String, Object>();
226         MediaPlayer player = getPlayer(tag);
227         result.put("tag", getDefault(tag));
228         if (player == null) {
229             result.put("loaded", false);
230         } else {
231             result.put("loaded", true);
232             result.put("duration", player.getDuration());
233             result.put("position", player.getCurrentPosition());
234             result.put("isplaying", player.isPlaying());
235             result.put("url", getUrl(tag));
236             result.put("looping", player.isLooping());
237         }
238         return result;
239     }
240 
241     @Rpc(description = "Lists currently loaded media", returns = "List of Media Tags")
mediaPlayList()242     public Set<String> mediaPlayList() {
243         return mPlayers.keySet();
244     }
245 
246     @Rpc(description = "Set Looping", returns = "True if successful")
mediaPlaySetLooping(@pcParametername = "enabled") @pcDefaultvalue = "true") Boolean enabled, @RpcParameter(name = "tag", description = "string identifying resource") @RpcDefault(value = "default") String tag)247     public synchronized boolean mediaPlaySetLooping(@RpcParameter(name = "enabled")
248     @RpcDefault(value = "true")
249     Boolean enabled, @RpcParameter(name = "tag", description = "string identifying resource")
250     @RpcDefault(value = "default")
251     String tag) {
252         MediaPlayer player = getPlayer(tag);
253         if (player == null) {
254             return false;
255         }
256         player.setLooping(enabled);
257         return true;
258     }
259 
260     @Rpc(description = "Checks if media file is playing.", returns = "true if playing")
mediaSetNext( @pcParametername = "tag", description = "string identifying resource") @pcDefaultvalue = "default") String tag, @RpcParameter(name = "next", description = "tag of the next track to play.") String next)261     public synchronized void mediaSetNext(
262             @RpcParameter(name = "tag", description = "string identifying resource")
263             @RpcDefault(value = "default")
264             String tag,
265             @RpcParameter(name = "next", description = "tag of the next track to play.")
266             String next) {
267         MediaPlayer player = getPlayer(tag);
268         MediaPlayer nPlayer = getPlayer(next);
269         if (player == null) {
270             throw new NullPointerException("Non-existent player tag " + tag);
271         }
272         if (nPlayer == null) {
273             throw new NullPointerException("Non-existent player tag " + next);
274         }
275         player.setNextMediaPlayer(nPlayer);
276     }
277 
278     @Override
shutdown()279     public synchronized void shutdown() {
280         for (String key : mPlayers.keySet()) {
281             MediaPlayer player = mPlayers.get(key);
282             if (player != null) {
283                 player.stop();
284                 player.release();
285                 player = null;
286             }
287         }
288         mPlayers.clear();
289         mUrls.clear();
290     }
291 
292     @Override
onCompletion(MediaPlayer player)293     public void onCompletion(MediaPlayer player) {
294         String tag = getTag(player);
295         if (tag != null) {
296             Map<String, Object> data = new HashMap<String, Object>();
297             data.put("action", "complete");
298             data.put("tag", tag);
299             mEventFacade.postEvent("media", data);
300         }
301     }
302 
getTag(MediaPlayer player)303     private String getTag(MediaPlayer player) {
304         for (Entry<String, MediaPlayer> m : mPlayers.entrySet()) {
305             if (m.getValue() == player) {
306                 return m.getKey();
307             }
308         }
309         return null;
310     }
311 }
312