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