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