1 /* 2 * Copyright (C) 2008 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 android.media; 18 19 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.res.AssetFileDescriptor; 22 import android.os.Build; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.util.AndroidRuntimeException; 27 import android.util.Log; 28 29 import java.io.FileDescriptor; 30 import java.lang.ref.WeakReference; 31 32 /** 33 * JetPlayer provides access to JET content playback and control. 34 * 35 * <p>Please refer to the 36 * <a href="https://developer.android.com/guide/topics/media/jet/jetcreator_manual">JET Creator User 37 * Manual</a> for a presentation of the JET interactive music concept and how to use the JetCreator 38 * tool to create content to be player by JetPlayer. 39 * 40 * <p>Use of the JetPlayer class is based around the playback of a number of JET segments 41 * sequentially added to a playback FIFO queue. The rendering of the MIDI content stored in each 42 * segment can be dynamically affected by two mechanisms: 43 * <ul> 44 * <li>Tracks in a segment can be muted or unmuted at any moment, individually or through a mask 45 * (to change the mute state of multiple tracks at once). 46 * <li>Parts of tracks in a segment can be played at predefined points in the segment, in order to 47 * maintain synchronization with the other tracks in the segment. This is achieved through the 48 * notion of "clips", which can be triggered at any time, but that will play only at the right 49 * time, as authored in the corresponding JET file. 50 * </ul> 51 * 52 * <p>As a result of the rendering and playback of the JET segments, the user of the JetPlayer 53 * instance can receive notifications from the JET engine relative to: 54 * <ul> 55 * <li>Playback state 56 * <li>Number of segments left to play in the queue 57 * <li>Application controller events (CC80-83) to mark points in the MIDI segments 58 * </ul> 59 * 60 * <p>Use {@link #getJetPlayer()} to construct a JetPlayer instance. JetPlayer is a singleton class. 61 */ 62 public class JetPlayer 63 { 64 //-------------------------------------------- 65 // Constants 66 //------------------------ 67 /** 68 * The maximum number of simultaneous tracks. Use {@link #getMaxTracks()} to 69 * access this value. 70 */ 71 private static int MAXTRACKS = 32; 72 73 // to keep in sync with the JetPlayer class constants 74 // defined in frameworks/base/include/media/JetPlayer.h 75 private static final int JET_EVENT = 1; 76 private static final int JET_USERID_UPDATE = 2; 77 private static final int JET_NUMQUEUEDSEGMENT_UPDATE = 3; 78 private static final int JET_PAUSE_UPDATE = 4; 79 80 // to keep in sync with external/sonivox/arm-wt-22k/lib_src/jet_data.h 81 // Encoding of event information on 32 bits 82 private static final int JET_EVENT_VAL_MASK = 0x0000007f; // mask for value 83 private static final int JET_EVENT_CTRL_MASK = 0x00003f80; // mask for controller 84 private static final int JET_EVENT_CHAN_MASK = 0x0003c000; // mask for channel 85 private static final int JET_EVENT_TRACK_MASK = 0x00fc0000; // mask for track number 86 private static final int JET_EVENT_SEG_MASK = 0xff000000; // mask for segment ID 87 private static final int JET_EVENT_CTRL_SHIFT = 7; // shift to get controller number to bit 0 88 private static final int JET_EVENT_CHAN_SHIFT = 14; // shift to get MIDI channel to bit 0 89 private static final int JET_EVENT_TRACK_SHIFT = 18; // shift to get track ID to bit 0 90 private static final int JET_EVENT_SEG_SHIFT = 24; // shift to get segment ID to bit 0 91 92 // to keep in sync with values used in external/sonivox/arm-wt-22k/Android.mk 93 // Jet rendering audio parameters 94 private static final int JET_OUTPUT_RATE = 22050; // _SAMPLE_RATE_22050 in Android.mk 95 private static final int JET_OUTPUT_CHANNEL_CONFIG = 96 AudioFormat.CHANNEL_OUT_STEREO; // NUM_OUTPUT_CHANNELS=2 in Android.mk 97 98 99 //-------------------------------------------- 100 // Member variables 101 //------------------------ 102 /** 103 * Handler for jet events and status updates coming from the native code 104 */ 105 private NativeEventHandler mEventHandler = null; 106 107 /** 108 * Looper associated with the thread that creates the AudioTrack instance 109 */ 110 private Looper mInitializationLooper = null; 111 112 /** 113 * Lock to protect the event listener updates against event notifications 114 */ 115 private final Object mEventListenerLock = new Object(); 116 117 private OnJetEventListener mJetEventListener = null; 118 119 private static JetPlayer singletonRef; 120 121 static { 122 System.loadLibrary("media_jni"); 123 } 124 125 //-------------------------------- 126 // Used exclusively by native code 127 //-------------------- 128 /** 129 * Accessed by native methods: provides access to C++ JetPlayer object 130 */ 131 @SuppressWarnings("unused") 132 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 133 private long mNativePlayerInJavaObj; 134 135 136 //-------------------------------------------- 137 // Constructor, finalize 138 //------------------------ 139 /** 140 * Factory method for the JetPlayer class. 141 * @return the singleton JetPlayer instance. 142 */ getJetPlayer()143 public static JetPlayer getJetPlayer() { 144 if (singletonRef == null) { 145 singletonRef = new JetPlayer(); 146 } 147 return singletonRef; 148 } 149 150 /** 151 * Cloning a JetPlayer instance is not supported. Calling clone() will generate an exception. 152 */ clone()153 public Object clone() throws CloneNotSupportedException { 154 // JetPlayer is a singleton class, 155 // so you can't clone a JetPlayer instance 156 throw new CloneNotSupportedException(); 157 } 158 159 JetPlayer()160 private JetPlayer() { 161 162 // remember which looper is associated with the JetPlayer instanciation 163 if ((mInitializationLooper = Looper.myLooper()) == null) { 164 mInitializationLooper = Looper.getMainLooper(); 165 } 166 167 int buffSizeInBytes = AudioTrack.getMinBufferSize(JET_OUTPUT_RATE, 168 JET_OUTPUT_CHANNEL_CONFIG, AudioFormat.ENCODING_PCM_16BIT); 169 170 if ((buffSizeInBytes != AudioTrack.ERROR) 171 && (buffSizeInBytes != AudioTrack.ERROR_BAD_VALUE)) { 172 173 native_setup(new WeakReference<JetPlayer>(this), 174 JetPlayer.getMaxTracks(), 175 // bytes to frame conversion: 176 // 1200 == minimum buffer size in frames on generation 1 hardware 177 Math.max(1200, buffSizeInBytes / 178 (AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT) * 179 2 /*channels*/))); 180 } 181 } 182 183 finalize()184 protected void finalize() { 185 native_finalize(); 186 } 187 188 189 /** 190 * Stops the current JET playback, and releases all associated native resources. 191 * The object can no longer be used and the reference should be set to null 192 * after a call to release(). 193 */ release()194 public void release() { 195 native_release(); 196 singletonRef = null; 197 } 198 199 200 //-------------------------------------------- 201 // Getters 202 //------------------------ 203 /** 204 * Gets the maximum number of simultaneous MIDI tracks supported by JetPlayer. 205 * @return the maximum number of simultaneous MIDI tracks supported by JetPlayer. 206 */ getMaxTracks()207 public static int getMaxTracks() { 208 return JetPlayer.MAXTRACKS; 209 } 210 211 212 //-------------------------------------------- 213 // Jet functionality 214 //------------------------ 215 /** 216 * Loads a .jet file from a given path. 217 * @param path the path to the .jet file, for instance "/sdcard/mygame/music.jet". 218 * @return true if loading the .jet file was successful, false if loading failed. 219 */ loadJetFile(String path)220 public boolean loadJetFile(String path) { 221 return native_loadJetFromFile(path); 222 } 223 224 225 /** 226 * Loads a .jet file from an asset file descriptor. 227 * @param afd the asset file descriptor. 228 * @return true if loading the .jet file was successful, false if loading failed. 229 */ loadJetFile(AssetFileDescriptor afd)230 public boolean loadJetFile(AssetFileDescriptor afd) { 231 long len = afd.getLength(); 232 if (len < 0) { 233 throw new AndroidRuntimeException("no length for fd"); 234 } 235 return native_loadJetFromFileD( 236 afd.getFileDescriptor(), afd.getStartOffset(), len); 237 } 238 239 /** 240 * Closes the resource containing the JET content. 241 * @return true if successfully closed, false otherwise. 242 */ closeJetFile()243 public boolean closeJetFile() { 244 return native_closeJetFile(); 245 } 246 247 248 /** 249 * Starts playing the JET segment queue. 250 * @return true if rendering and playback is successfully started, false otherwise. 251 */ play()252 public boolean play() { 253 return native_playJet(); 254 } 255 256 257 /** 258 * Pauses the playback of the JET segment queue. 259 * @return true if rendering and playback is successfully paused, false otherwise. 260 */ pause()261 public boolean pause() { 262 return native_pauseJet(); 263 } 264 265 266 /** 267 * Queues the specified segment in the JET queue. 268 * @param segmentNum the identifier of the segment. 269 * @param libNum the index of the sound bank associated with the segment. Use -1 to indicate 270 * that no sound bank (DLS file) is associated with this segment, in which case JET will use 271 * the General MIDI library. 272 * @param repeatCount the number of times the segment will be repeated. 0 means the segment will 273 * only play once. -1 means the segment will repeat indefinitely. 274 * @param transpose the amount of pitch transposition. Set to 0 for normal playback. 275 * Range is -12 to +12. 276 * @param muteFlags a bitmask to specify which MIDI tracks will be muted during playback. Bit 0 277 * affects track 0, bit 1 affects track 1 etc. 278 * @param userID a value specified by the application that uniquely identifies the segment. 279 * this value is received in the 280 * {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method. 281 * Normally, the application will keep a byte value that is incremented each time a new 282 * segment is queued up. This can be used to look up any special characteristics of that 283 * track including trigger clips and mute flags. 284 * @return true if the segment was successfully queued, false if the queue is full or if the 285 * parameters are invalid. 286 */ queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)287 public boolean queueJetSegment(int segmentNum, int libNum, int repeatCount, 288 int transpose, int muteFlags, byte userID) { 289 return native_queueJetSegment(segmentNum, libNum, repeatCount, 290 transpose, muteFlags, userID); 291 } 292 293 294 /** 295 * Queues the specified segment in the JET queue. 296 * @param segmentNum the identifier of the segment. 297 * @param libNum the index of the soundbank associated with the segment. Use -1 to indicate that 298 * no sound bank (DLS file) is associated with this segment, in which case JET will use 299 * the General MIDI library. 300 * @param repeatCount the number of times the segment will be repeated. 0 means the segment will 301 * only play once. -1 means the segment will repeat indefinitely. 302 * @param transpose the amount of pitch transposition. Set to 0 for normal playback. 303 * Range is -12 to +12. 304 * @param muteArray an array of booleans to specify which MIDI tracks will be muted during 305 * playback. The value at index 0 affects track 0, value at index 1 affects track 1 etc. 306 * The length of the array must be {@link #getMaxTracks()} for the call to succeed. 307 * @param userID a value specified by the application that uniquely identifies the segment. 308 * this value is received in the 309 * {@link OnJetEventListener#onJetUserIdUpdate(JetPlayer, int, int)} event listener method. 310 * Normally, the application will keep a byte value that is incremented each time a new 311 * segment is queued up. This can be used to look up any special characteristics of that 312 * track including trigger clips and mute flags. 313 * @return true if the segment was successfully queued, false if the queue is full or if the 314 * parameters are invalid. 315 */ queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount, int transpose, boolean[] muteArray, byte userID)316 public boolean queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount, 317 int transpose, boolean[] muteArray, byte userID) { 318 if (muteArray.length != JetPlayer.getMaxTracks()) { 319 return false; 320 } 321 return native_queueJetSegmentMuteArray(segmentNum, libNum, repeatCount, 322 transpose, muteArray, userID); 323 } 324 325 326 /** 327 * Modifies the mute flags. 328 * @param muteFlags a bitmask to specify which MIDI tracks are muted. Bit 0 affects track 0, 329 * bit 1 affects track 1 etc. 330 * @param sync if false, the new mute flags will be applied as soon as possible by the JET 331 * render and playback engine. If true, the mute flags will be updated at the start of the 332 * next segment. If the segment is repeated, the flags will take effect the next time 333 * segment is repeated. 334 * @return true if the mute flags were successfully updated, false otherwise. 335 */ setMuteFlags(int muteFlags, boolean sync)336 public boolean setMuteFlags(int muteFlags, boolean sync) { 337 return native_setMuteFlags(muteFlags, sync); 338 } 339 340 341 /** 342 * Modifies the mute flags for the current active segment. 343 * @param muteArray an array of booleans to specify which MIDI tracks are muted. The value at 344 * index 0 affects track 0, value at index 1 affects track 1 etc. 345 * The length of the array must be {@link #getMaxTracks()} for the call to succeed. 346 * @param sync if false, the new mute flags will be applied as soon as possible by the JET 347 * render and playback engine. If true, the mute flags will be updated at the start of the 348 * next segment. If the segment is repeated, the flags will take effect the next time 349 * segment is repeated. 350 * @return true if the mute flags were successfully updated, false otherwise. 351 */ setMuteArray(boolean[] muteArray, boolean sync)352 public boolean setMuteArray(boolean[] muteArray, boolean sync) { 353 if(muteArray.length != JetPlayer.getMaxTracks()) 354 return false; 355 return native_setMuteArray(muteArray, sync); 356 } 357 358 359 /** 360 * Mutes or unmutes a single track. 361 * @param trackId the index of the track to mute. 362 * @param muteFlag set to true to mute, false to unmute. 363 * @param sync if false, the new mute flags will be applied as soon as possible by the JET 364 * render and playback engine. If true, the mute flag will be updated at the start of the 365 * next segment. If the segment is repeated, the flag will take effect the next time 366 * segment is repeated. 367 * @return true if the mute flag was successfully updated, false otherwise. 368 */ setMuteFlag(int trackId, boolean muteFlag, boolean sync)369 public boolean setMuteFlag(int trackId, boolean muteFlag, boolean sync) { 370 return native_setMuteFlag(trackId, muteFlag, sync); 371 } 372 373 374 /** 375 * Schedules the playback of a clip. 376 * This will automatically update the mute flags in sync with the JET Clip Marker (controller 377 * 103). The parameter clipID must be in the range of 0-63. After the call to triggerClip, when 378 * JET next encounters a controller event 103 with bits 0-5 of the value equal to clipID and 379 * bit 6 set to 1, it will automatically unmute the track containing the controller event. 380 * When JET encounters the complementary controller event 103 with bits 0-5 of the value equal 381 * to clipID and bit 6 set to 0, it will mute the track again. 382 * @param clipId the identifier of the clip to trigger. 383 * @return true if the clip was successfully triggered, false otherwise. 384 */ triggerClip(int clipId)385 public boolean triggerClip(int clipId) { 386 return native_triggerClip(clipId); 387 } 388 389 390 /** 391 * Empties the segment queue, and clears all clips that are scheduled for playback. 392 * @return true if the queue was successfully cleared, false otherwise. 393 */ clearQueue()394 public boolean clearQueue() { 395 return native_clearQueue(); 396 } 397 398 399 //--------------------------------------------------------- 400 // Internal class to handle events posted from native code 401 //------------------------ 402 private class NativeEventHandler extends Handler 403 { 404 private JetPlayer mJet; 405 NativeEventHandler(JetPlayer jet, Looper looper)406 public NativeEventHandler(JetPlayer jet, Looper looper) { 407 super(looper); 408 mJet = jet; 409 } 410 411 @Override handleMessage(Message msg)412 public void handleMessage(Message msg) { 413 OnJetEventListener listener = null; 414 synchronized (mEventListenerLock) { 415 listener = mJet.mJetEventListener; 416 } 417 switch(msg.what) { 418 case JET_EVENT: 419 if (listener != null) { 420 // call the appropriate listener after decoding the event parameters 421 // encoded in msg.arg1 422 mJetEventListener.onJetEvent( 423 mJet, 424 (short)((msg.arg1 & JET_EVENT_SEG_MASK) >> JET_EVENT_SEG_SHIFT), 425 (byte) ((msg.arg1 & JET_EVENT_TRACK_MASK) >> JET_EVENT_TRACK_SHIFT), 426 // JETCreator channel numbers start at 1, but the index starts at 0 427 // in the .jet files 428 (byte)(((msg.arg1 & JET_EVENT_CHAN_MASK) >> JET_EVENT_CHAN_SHIFT) + 1), 429 (byte) ((msg.arg1 & JET_EVENT_CTRL_MASK) >> JET_EVENT_CTRL_SHIFT), 430 (byte) (msg.arg1 & JET_EVENT_VAL_MASK) ); 431 } 432 return; 433 case JET_USERID_UPDATE: 434 if (listener != null) { 435 listener.onJetUserIdUpdate(mJet, msg.arg1, msg.arg2); 436 } 437 return; 438 case JET_NUMQUEUEDSEGMENT_UPDATE: 439 if (listener != null) { 440 listener.onJetNumQueuedSegmentUpdate(mJet, msg.arg1); 441 } 442 return; 443 case JET_PAUSE_UPDATE: 444 if (listener != null) 445 listener.onJetPauseUpdate(mJet, msg.arg1); 446 return; 447 448 default: 449 loge("Unknown message type " + msg.what); 450 return; 451 } 452 } 453 } 454 455 456 //-------------------------------------------- 457 // Jet event listener 458 //------------------------ 459 /** 460 * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and 461 * playback engine. Notifications are received in the same thread as the one in which the 462 * JetPlayer instance was created. 463 * @param listener the listener that will be notified when a JET event is generated. 464 */ setEventListener(OnJetEventListener listener)465 public void setEventListener(OnJetEventListener listener) { 466 setEventListener(listener, null); 467 } 468 469 /** 470 * Sets the listener JetPlayer notifies when a JET event is generated by the rendering and 471 * playback engine. Use this method to receive JET events in the Handler associated with 472 * another thread than the one in which you created the JetPlayer instance. 473 * @param listener the listener that will be notified when a JET event is generated. 474 * @param handler the Handler that will receive the event notification messages. 475 */ setEventListener(OnJetEventListener listener, Handler handler)476 public void setEventListener(OnJetEventListener listener, Handler handler) { 477 synchronized(mEventListenerLock) { 478 479 mJetEventListener = listener; 480 481 if (listener != null) { 482 if (handler != null) { 483 mEventHandler = new NativeEventHandler(this, handler.getLooper()); 484 } else { 485 // no given handler, use the looper the AudioTrack was created in 486 mEventHandler = new NativeEventHandler(this, mInitializationLooper); 487 } 488 } else { 489 mEventHandler = null; 490 } 491 492 } 493 } 494 495 496 /** 497 * Handles the notification when the JET engine generates an event. 498 */ 499 public interface OnJetEventListener { 500 /** 501 * Callback for when the JET engine generates a new event. 502 * 503 * @param player the JET player the event is coming from 504 * @param segment 8 bit unsigned value 505 * @param track 6 bit unsigned value 506 * @param channel 4 bit unsigned value 507 * @param controller 7 bit unsigned value 508 * @param value 7 bit unsigned value 509 */ onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value)510 void onJetEvent(JetPlayer player, 511 short segment, byte track, byte channel, byte controller, byte value); 512 /** 513 * Callback for when JET's currently playing segment's userID is updated. 514 * 515 * @param player the JET player the status update is coming from 516 * @param userId the ID of the currently playing segment 517 * @param repeatCount the repetition count for the segment (0 means it plays once) 518 */ onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount)519 void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount); 520 521 /** 522 * Callback for when JET's number of queued segments is updated. 523 * 524 * @param player the JET player the status update is coming from 525 * @param nbSegments the number of segments in the JET queue 526 */ onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments)527 void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments); 528 529 /** 530 * Callback for when JET pause state is updated. 531 * 532 * @param player the JET player the status update is coming from 533 * @param paused indicates whether JET is paused (1) or not (0) 534 */ onJetPauseUpdate(JetPlayer player, int paused)535 void onJetPauseUpdate(JetPlayer player, int paused); 536 } 537 538 539 //-------------------------------------------- 540 // Native methods 541 //------------------------ native_setup(Object Jet_this, int maxTracks, int trackBufferSize)542 private native final boolean native_setup(Object Jet_this, 543 int maxTracks, int trackBufferSize); native_finalize()544 private native final void native_finalize(); native_release()545 private native final void native_release(); native_loadJetFromFile(String pathToJetFile)546 private native final boolean native_loadJetFromFile(String pathToJetFile); native_loadJetFromFileD(FileDescriptor fd, long offset, long len)547 private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len); native_closeJetFile()548 private native final boolean native_closeJetFile(); native_playJet()549 private native final boolean native_playJet(); native_pauseJet()550 private native final boolean native_pauseJet(); native_queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)551 private native final boolean native_queueJetSegment(int segmentNum, int libNum, 552 int repeatCount, int transpose, int muteFlags, byte userID); native_queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount, int transpose, boolean[] muteArray, byte userID)553 private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum, 554 int repeatCount, int transpose, boolean[] muteArray, byte userID); native_setMuteFlags(int muteFlags, boolean sync)555 private native final boolean native_setMuteFlags(int muteFlags, boolean sync); native_setMuteArray(boolean[]muteArray, boolean sync)556 private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync); native_setMuteFlag(int trackId, boolean muteFlag, boolean sync)557 private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync); native_triggerClip(int clipId)558 private native final boolean native_triggerClip(int clipId); native_clearQueue()559 private native final boolean native_clearQueue(); 560 561 //--------------------------------------------------------- 562 // Called exclusively by native code 563 //-------------------- 564 @SuppressWarnings("unused") 565 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) postEventFromNative(Object jetplayer_ref, int what, int arg1, int arg2)566 private static void postEventFromNative(Object jetplayer_ref, 567 int what, int arg1, int arg2) { 568 //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2); 569 JetPlayer jet = (JetPlayer)((WeakReference)jetplayer_ref).get(); 570 571 if ((jet != null) && (jet.mEventHandler != null)) { 572 Message m = 573 jet.mEventHandler.obtainMessage(what, arg1, arg2, null); 574 jet.mEventHandler.sendMessage(m); 575 } 576 577 } 578 579 580 //--------------------------------------------------------- 581 // Utils 582 //-------------------- 583 private final static String TAG = "JetPlayer-J"; 584 logd(String msg)585 private static void logd(String msg) { 586 Log.d(TAG, "[ android.media.JetPlayer ] " + msg); 587 } 588 loge(String msg)589 private static void loge(String msg) { 590 Log.e(TAG, "[ android.media.JetPlayer ] " + msg); 591 } 592 593 } 594