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