7 package com.google.appinventor.components.runtime;
9 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
11 import android.app.Activity;
12 import android.content.Context;
13 import android.media.AudioManager;
14 import android.media.MediaPlayer;
15 import android.media.MediaPlayer.OnCompletionListener;
16 import android.os.Vibrator;
33 import java.io.IOException;
66 @DesignerComponent(version = YaVersion.PLAYER_COMPONENT_VERSION,
67 description =
"Multimedia component that plays audio and " +
68 "controls phone vibration. The name of a multimedia field is " +
69 "specified in the <code>Source</code> property, which can be set in " +
70 "the Designer or in the Blocks Editor. The length of time for a " +
71 "vibration is specified in the Blocks Editor in milliseconds " +
72 "(thousandths of a second).\n" +
73 "<p>For supported audio formats, see " +
74 "<a href=\"http://developer.android.com/guide/appendix/media-formats.html\"" +
75 " target=\"_blank\">Android Supported Media Formats</a>.</p>\n" +
76 "<p>This component is best for long sound files, such as songs, " +
77 "while the <code>Sound</code> component is more efficient for short " +
78 "files, such as sound effects.</p>",
79 category = ComponentCategory.MEDIA,
81 iconName =
"images/player.png")
83 @UsesPermissions(permissionNames =
"android.permission.VIBRATE, android.permission.INTERNET")
87 private MediaPlayer player;
88 private final Vibrator vibe;
91 public enum State { INITIAL,
PREPARED, PLAYING, PAUSED_BY_USER, PAUSED_BY_EVENT; }
92 private String sourcePath;
98 private boolean playOnlyInForeground;
100 private boolean focusOn;
101 private AudioManager am;
102 private final Activity activity;
104 private static final boolean audioFocusSupported;
105 private Object afChangeListener;
109 audioFocusSupported =
true;
111 audioFocusSupported =
false;
138 public Player(ComponentContainer container) {
139 super(container.$form());
140 activity = container.$context();
142 vibe = (Vibrator) form.getSystemService(Context.VIBRATOR_SERVICE);
143 form.registerForOnDestroy(
this);
144 form.registerForOnResume(
this);
145 form.registerForOnPause(
this);
146 form.registerForOnStop(
this);
148 form.setVolumeControlStream(AudioManager.STREAM_MUSIC);
150 playOnlyInForeground =
false;
152 am = (audioFocusSupported) ? FroyoUtil.setAudioManager(activity) :
null;
153 afChangeListener = (audioFocusSupported) ? FroyoUtil.setAudioFocusChangeListener(
this) :
null;
160 category = PropertyCategory.BEHAVIOR)
161 public String Source() {
174 @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_ASSET,
177 @UsesPermissions(READ_EXTERNAL_STORAGE)
178 public void Source(String path) {
179 final String tempPath = (path ==
null) ?
"" : path;
180 if (MediaUtil.isExternalFile(form, tempPath)
181 && form.isDeniedPermission(READ_EXTERNAL_STORAGE)) {
182 form.askPermission(READ_EXTERNAL_STORAGE,
new PermissionResultHandler() {
184 public void HandlePermissionResponse(String permission,
boolean granted) {
186 Player.this.Source(tempPath);
188 form.dispatchPermissionDeniedEvent(Player.this,
"Source", permission);
195 sourcePath = tempPath;
198 if (playerState == State.PREPARED || playerState == State.PLAYING || playerState == State.PAUSED_BY_USER) {
202 if (player !=
null) {
207 if (sourcePath.length() > 0) {
208 player =
new MediaPlayer();
209 player.setOnCompletionListener(
this);
212 MediaUtil.loadMediaPlayer(player, form, sourcePath);
214 }
catch (PermissionException e) {
217 form.dispatchPermissionDeniedEvent(
this,
"Source", e);
219 }
catch (IOException e) {
222 form.dispatchErrorOccurredEvent(
this,
"Source",
223 ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, sourcePath);
227 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
228 if (audioFocusSupported) {
229 requestPermanentFocus();
240 private void requestPermanentFocus() {
242 focusOn = (FroyoUtil.focusRequestGranted(am, afChangeListener)) ?
true :
false;
244 form.dispatchErrorOccurredEvent(
this,
"Source",
245 ErrorMessages.ERROR_UNABLE_TO_FOCUS_MEDIA, sourcePath);
252 description =
"Reports whether the media is playing",
253 category = PropertyCategory.BEHAVIOR)
254 public
boolean IsPlaying() {
255 if (playerState == State.PREPARED || playerState == State.PLAYING) {
256 return player.isPlaying();
265 description =
"If true, the player will loop when it plays. Setting Loop while the player " +
266 "is playing will affect the current playing.",
267 category = PropertyCategory.BEHAVIOR)
268 public
boolean Loop() {
279 editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
280 defaultValue =
"False")
282 public void Loop(
boolean shouldLoop) {
284 if (playerState == State.PREPARED || playerState == State.PLAYING || playerState == State.PAUSED_BY_USER) {
285 player.setLooping(shouldLoop);
298 editorType = PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_FLOAT,
301 description =
"Sets the volume to a number between 0 and 100")
302 public
void Volume(
int vol) {
303 if (playerState == State.PREPARED || playerState == State.PLAYING || playerState == State.PAUSED_BY_USER) {
304 if (vol > 100 || vol < 0) {
305 form.dispatchErrorOccurredEvent(
this,
"Volume", ErrorMessages.ERROR_PLAYER_INVALID_VOLUME, vol);
307 player.setVolume(((
float) vol) / 100, ((
float) vol) / 100);
318 description =
"If true, the player will pause playing when leaving the current screen; " +
319 "if false (default option), the player continues playing"+
320 " whenever the current screen is displaying or not.",
321 category = PropertyCategory.BEHAVIOR)
322 public
boolean PlayOnlyInForeground() {
323 return playOnlyInForeground;
334 editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
335 defaultValue =
"False")
337 public void PlayOnlyInForeground(
boolean shouldForeground) {
338 playOnlyInForeground = shouldForeground;
346 public void Start() {
347 if (audioFocusSupported && !focusOn) {
348 requestPermanentFocus();
350 if (playerState == State.PREPARED || playerState == State.PLAYING || playerState == State.PAUSED_BY_USER || playerState == State.PAUSED_BY_EVENT ) {
351 player.setLooping(loop);
362 public void Pause() {
363 if (player ==
null)
return;
364 boolean wasPlaying = player.isPlaying();
365 if (playerState == State.PLAYING) {
378 public void pause() {
379 if (player ==
null)
return;
380 if (playerState == State.PLAYING) {
392 if (audioFocusSupported && focusOn) {
395 if (playerState == State.PLAYING || playerState == State.PAUSED_BY_USER || playerState == State.PAUSED_BY_EVENT) {
398 if (player !=
null) {
408 private void abandonFocus() {
410 FroyoUtil.abandonFocus(am, afChangeListener);
419 public void Vibrate(
long milliseconds) {
420 vibe.vibrate(milliseconds);
423 @SimpleEvent(description =
"The PlayerError event is no longer used. " +
424 "Please use the Screen.ErrorOccurred event instead.",
426 public
void PlayerError(String message) {
429 private void prepare() {
435 }
catch (IOException ioe) {
439 form.dispatchErrorOccurredEvent(
this,
"Source",
440 ErrorMessages.ERROR_UNABLE_TO_PREPARE_MEDIA, sourcePath);
446 public void onCompletion(MediaPlayer m) {
454 public void Completed() {
456 if (audioFocusSupported && focusOn) {
460 EventDispatcher.dispatchEvent(
this,
"Completed");
467 @SimpleEvent(description =
"This event is signaled when another player has started" +
468 " (and the current player is playing or paused, but not stopped).")
469 public
void OtherPlayerStarted() {
470 EventDispatcher.dispatchEvent(
this,
"OtherPlayerStarted");
475 public void onResume() {
476 if (playOnlyInForeground && playerState == State.PAUSED_BY_EVENT) {
484 public void onPause() {
485 if (player ==
null)
return;
486 if (playOnlyInForeground && player.isPlaying()) {
492 public void onStop() {
493 if (player ==
null)
return;
494 if (playOnlyInForeground && player.isPlaying()) {
501 public void onDestroy() {
507 public void onDelete() {
511 private void prepareToDie() {
513 if (audioFocusSupported && focusOn) {
516 if ((player !=
null) && (playerState != State.INITIAL)) {
520 if (player !=
null) {