1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under 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 */ 71 72 public class MediaPlayerFacade extends RpcReceiver implements MediaPlayer.OnCompletionListener { 73 74 private final Service mService; 75 static private final Map<String, MediaPlayer> mPlayers = new Hashtable<String, MediaPlayer>(); 76 static private final Map<String, String> mUrls = new Hashtable<String, String>(); 77 78 private final EventFacade mEventFacade; 79 80 public MediaPlayerFacade(FacadeManager manager) { 81 super(manager); 82 mService = manager.getService(); 83 mEventFacade = manager.getReceiver(EventFacade.class); 84 } 85 86 private String getDefault(String tag) { 87 return (tag == null || tag.equals("")) ? "default" : tag; 88 } 89 90 private MediaPlayer getPlayer(String tag) { 91 tag = getDefault(tag); 92 return mPlayers.get(tag); 93 } 94 95 private String getUrl(String tag) { 96 tag = getDefault(tag); 97 return mUrls.get(tag); 98 } 99 100 private void putMp(String tag, MediaPlayer player, String url) { 101 tag = getDefault(tag); 102 mPlayers.put(tag, player); 103 mUrls.put(tag, url); 104 } 105 106 private void removeMp(String tag) { 107 tag = getDefault(tag); 108 MediaPlayer player = mPlayers.get(tag); 109 if (player != null) { 110 player.stop(); 111 player.release(); 112 } 113 mPlayers.remove(tag); 114 mUrls.remove(tag); 115 } 116 117 @Rpc(description = "Open a media file", returns = "true if play successful") 118 public synchronized boolean mediaPlayOpen(@RpcParameter(name = "url", 119 description = "url of media resource") 120 String url, @RpcParameter(name = "tag", description = "string identifying resource") 121 @RpcDefault(value = "default") 122 String tag, @RpcParameter(name = "play", description = "start playing immediately") 123 @RpcDefault(value = "true") 124 Boolean play) { 125 removeMp(tag); 126 MediaPlayer player = getPlayer(tag); 127 player = MediaPlayer.create(mService, Uri.parse(url)); 128 if (player != null) { 129 putMp(tag, player, url); 130 player.setOnCompletionListener(this); 131 if (play) { 132 player.start(); 133 } 134 } 135 return player != null; 136 } 137 138 @Rpc(description = "pause playing media file", returns = "true if successful") 139 public synchronized boolean mediaPlayPause( 140 @RpcParameter(name = "tag", description = "string identifying resource") 141 @RpcDefault(value = "default") 142 String tag) { 143 MediaPlayer player = getPlayer(tag); 144 if (player == null) { 145 return false; 146 } 147 player.pause(); 148 return true; 149 } 150 151 @Rpc(description = "Start playing media file.", returns = "true if successful") 152 public synchronized boolean mediaPlayStart( 153 @RpcParameter(name = "tag", description = "string identifying resource") 154 @RpcDefault(value = "default") 155 String tag) { 156 MediaPlayer player = getPlayer(tag); 157 if (player == null) { 158 return false; 159 } 160 player.start(); 161 return mediaIsPlaying(tag); 162 } 163 164 @Rpc(description = "Stop playing media file.", returns = "true if successful") 165 public synchronized boolean mediaPlayStop( 166 @RpcParameter(name = "tag", description = "string identifying resource") 167 @RpcDefault(value = "default") 168 String tag) { 169 MediaPlayer player = getPlayer(tag); 170 if (player == null) { 171 return false; 172 } 173 player.stop(); 174 return !mediaIsPlaying(tag) && player.getCurrentPosition() == 0; 175 } 176 177 @Rpc(description = "Stop all players.") 178 public synchronized void mediaPlayStopAll() { 179 for (MediaPlayer p : mPlayers.values()) { 180 p.stop(); 181 } 182 } 183 184 @Rpc(description = "Seek To Position", returns = "New Position (in ms)") 185 public synchronized int mediaPlaySeek(@RpcParameter(name = "msec", 186 description = "Position in millseconds") 187 Integer msec, @RpcParameter(name = "tag", description = "string identifying resource") 188 @RpcDefault(value = "default") 189 String tag) { 190 MediaPlayer player = getPlayer(tag); 191 if (player == null) { 192 return 0; 193 } 194 player.seekTo(msec); 195 return player.getCurrentPosition(); 196 } 197 198 @Rpc(description = "Close media file", returns = "true if successful") 199 public synchronized boolean mediaPlayClose( 200 @RpcParameter(name = "tag", description = "string identifying resource") 201 @RpcDefault(value = "default") 202 String tag) throws Exception { 203 if (!mPlayers.containsKey(tag)) { 204 return false; 205 } 206 removeMp(tag); 207 return true; 208 } 209 210 @Rpc(description = "Checks if media file is playing.", returns = "true if playing") 211 public synchronized boolean mediaIsPlaying( 212 @RpcParameter(name = "tag", description = "string identifying resource") 213 @RpcDefault(value = "default") 214 String tag) { 215 MediaPlayer player = getPlayer(tag); 216 return (player == null) ? false : player.isPlaying(); 217 } 218 219 @Rpc(description = "Information on current media", returns = "Media Information") 220 public synchronized Map<String, Object> mediaPlayGetInfo( 221 @RpcParameter(name = "tag", description = "string identifying resource") 222 @RpcDefault(value = "default") 223 String tag) { 224 Map<String, Object> result = new HashMap<String, Object>(); 225 MediaPlayer player = getPlayer(tag); 226 result.put("tag", getDefault(tag)); 227 if (player == null) { 228 result.put("loaded", false); 229 } else { 230 result.put("loaded", true); 231 result.put("duration", player.getDuration()); 232 result.put("position", player.getCurrentPosition()); 233 result.put("isplaying", player.isPlaying()); 234 result.put("url", getUrl(tag)); 235 result.put("looping", player.isLooping()); 236 } 237 return result; 238 } 239 240 @Rpc(description = "Lists currently loaded media", returns = "List of Media Tags") 241 public Set<String> mediaPlayList() { 242 return mPlayers.keySet(); 243 } 244 245 @Rpc(description = "Set Looping", returns = "True if successful") 246 public synchronized boolean mediaPlaySetLooping(@RpcParameter(name = "enabled") 247 @RpcDefault(value = "true") 248 Boolean enabled, @RpcParameter(name = "tag", description = "string identifying resource") 249 @RpcDefault(value = "default") 250 String tag) { 251 MediaPlayer player = getPlayer(tag); 252 if (player == null) { 253 return false; 254 } 255 player.setLooping(enabled); 256 return true; 257 } 258 259 @Rpc(description = "Checks if media file is playing.", returns = "true if playing") 260 public synchronized void mediaSetNext( 261 @RpcParameter(name = "tag", description = "string identifying resource") 262 @RpcDefault(value = "default") 263 String tag, 264 @RpcParameter(name = "next", description = "tag of the next track to play.") 265 String next) { 266 MediaPlayer player = getPlayer(tag); 267 MediaPlayer nPlayer = getPlayer(next); 268 if (player == null) { 269 throw new NullPointerException("Non-existent player tag " + tag); 270 } 271 if (nPlayer == null) { 272 throw new NullPointerException("Non-existent player tag " + next); 273 } 274 player.setNextMediaPlayer(nPlayer); 275 } 276 277 @Override 278 public synchronized void shutdown() { 279 for (String key : mPlayers.keySet()) { 280 MediaPlayer player = mPlayers.get(key); 281 if (player != null) { 282 player.stop(); 283 player.release(); 284 player = null; 285 } 286 } 287 mPlayers.clear(); 288 mUrls.clear(); 289 } 290 291 @Override 292 public void onCompletion(MediaPlayer player) { 293 String tag = getTag(player); 294 if (tag != null) { 295 Map<String, Object> data = new HashMap<String, Object>(); 296 data.put("action", "complete"); 297 data.put("tag", tag); 298 mEventFacade.postEvent("media", data); 299 } 300 } 301 302 private String getTag(MediaPlayer player) { 303 for (Entry<String, MediaPlayer> m : mPlayers.entrySet()) { 304 if (m.getValue() == player) { 305 return m.getKey(); 306 } 307 } 308 return null; 309 } 310 } 311