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