001/*
002 * Copyright (C) 2006 The Android Open Source Project
003 * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package com.baidu.cloud.media.player;
019
020import android.annotation.SuppressLint;
021import android.annotation.TargetApi;
022import android.content.ContentResolver;
023import android.content.Context;
024import android.content.SharedPreferences;
025import android.content.res.AssetFileDescriptor;
026import android.graphics.Rect;
027import android.graphics.SurfaceTexture;
028import android.media.MediaCodecInfo;
029import android.media.MediaCodecList;
030import android.media.RingtoneManager;
031import android.net.Uri;
032import android.os.Build;
033import android.os.Bundle;
034import android.os.Handler;
035import android.os.Looper;
036import android.os.Message;
037import android.os.ParcelFileDescriptor;
038import android.os.PowerManager;
039import android.provider.Settings;
040import android.text.TextUtils;
041import android.util.Log;
042import android.view.Surface;
043import android.view.SurfaceHolder;
044
045import com.baidu.cloud.media.download.LocalHlsSec;
046import com.baidu.cloud.media.player.annotations.AccessedByNative;
047import com.baidu.cloud.media.player.annotations.CalledByNative;
048import com.baidu.cloud.media.player.apm.APMEventHandle;
049import com.baidu.cloud.media.player.misc.BDCloudTrackInfo;
050import com.baidu.cloud.media.player.misc.IMediaDataSource;
051import com.baidu.cloud.media.player.misc.ITrackInfo;
052import com.baidu.cloud.media.player.pragma.DebugLog;
053
054import org.json.JSONArray;
055import org.json.JSONObject;
056
057import java.io.FileDescriptor;
058import java.io.FileNotFoundException;
059import java.io.IOException;
060import java.lang.ref.WeakReference;
061import java.lang.reflect.Field;
062import java.security.InvalidParameterException;
063import java.text.SimpleDateFormat;
064import java.util.ArrayList;
065import java.util.Date;
066import java.util.Locale;
067import java.util.Map;
068import java.util.TimeZone;
069import java.util.Timer;
070import java.util.TimerTask;
071
072/**
073 * 播放器核心类
074 * 该类的接口与安卓系统内置的MediaPlayer类似
075 */
076public final class BDCloudMediaPlayer extends AbstractMediaPlayer {
077    private static final String TAG = BDCloudMediaPlayer.class.getName();
078
079    public static final String SDK_VERSION = "2.3.1";
080    private static String mAK = "";
081    private static String sCuid = "";
082    private static String playId = "";
083    private static boolean sEnableP2P = false;
084    private static boolean sEnableCache = false;
085    private static String sCacheRootPath = null;
086
087    private static final int MEDIA_NOP = 0; // interface test message
088    private static final int MEDIA_PREPARED = 1;
089    private static final int MEDIA_PLAYBACK_STOPPED = 2;
090    private static final int MEDIA_PLAYBACK_COMPLETE = 3;
091    private static final int MEDIA_BUFFERING_UPDATE = 4;
092    private static final int MEDIA_SEEK_COMPLETE = 5;
093    private static final int MEDIA_SET_VIDEO_SIZE = 6;
094    private static final int MEDIA_TIMED_TEXT = 99;
095    private static final int MEDIA_ERROR = 100;
096    private static final int MEDIA_INFO = 200;
097
098    protected static final int MEDIA_SET_VIDEO_SAR = 10001;
099
100    private static final int AVAPP_EVENT_METADATA = 0x13000;
101    private static final int AVAPP_EVENT_DNS = 0x14000;
102    private static final int AVAPP_EVENT_HTTP_CONNECT_END = 0x30002;
103
104    // ----------------------------------------
105    // options
106    private static final int IJK_LOG_UNKNOWN = 0;
107    private static final int IJK_LOG_DEFAULT = 1;
108
109    private static final int IJK_LOG_VERBOSE = 2;
110    private static final int IJK_LOG_DEBUG = 3;
111    private static final int IJK_LOG_INFO = 4;
112    private static final int IJK_LOG_WARN = 5;
113    private static final int IJK_LOG_ERROR = 6;
114    private static final int IJK_LOG_FATAL = 7;
115    private static final int IJK_LOG_SILENT = 8;
116
117    private static final int OPT_CATEGORY_FORMAT = 1;
118    private static final int OPT_CATEGORY_CODEC = 2;
119    private static final int OPT_CATEGORY_SWS = 3;
120    private static final int OPT_CATEGORY_PLAYER = 4;
121
122    private static final int SDL_FCC_YV12 = 0x32315659; // YV12
123    private static final int SDL_FCC_RV16 = 0x36315652; // RGB565
124    private static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888
125    // ----------------------------------------
126
127    // ----------------------------------------
128    // properties
129    private static final int PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND = 10001;
130    private static final int PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND = 10002;
131    private static final int FFP_PROP_FLOAT_PLAYBACK_RATE = 10003;
132
133    private static final int FFP_PROP_INT64_SELECTED_VIDEO_STREAM = 20001;
134    private static final int FFP_PROP_INT64_SELECTED_AUDIO_STREAM = 20002;
135    private static final int FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM = 20011;
136
137    private static final int FFP_PROP_INT64_VIDEO_DECODER = 20003;
138    private static final int FFP_PROP_INT64_AUDIO_DECODER = 20004;
139    private static final int FFP_PROPV_DECODER_UNKNOWN = 0;
140    private static final int FFP_PROPV_DECODER_AVCODEC = 1;
141    private static final int FFP_PROPV_DECODER_MEDIACODEC = 2;
142    private static final int FFP_PROPV_DECODER_VIDEOTOOLBOX = 3;
143    private static final int FFP_PROP_INT64_VIDEO_CACHED_DURATION = 20005;
144    private static final int FFP_PROP_INT64_AUDIO_CACHED_DURATION = 20006;
145    private static final int FFP_PROP_INT64_VIDEO_CACHED_BYTES = 20007;
146    private static final int FFP_PROP_INT64_AUDIO_CACHED_BYTES = 20008;
147    private static final int FFP_PROP_INT64_VIDEO_CACHED_PACKETS = 20009;
148    private static final int FFP_PROP_INT64_AUDIO_CACHED_PACKETS = 20010;
149    private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS = 20201;
150    private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS = 20202;
151    private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY = 20203;
152    private static final int FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT = 20204;
153    private static final int FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS = 20205;
154    private static final int FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS = 20206;
155    private static final int FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS = 20207;
156    private static final int FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES = 20208;
157    private static final int FFP_PROP_INT64_BIT_RATE = 20100;
158    private static final int FFP_PROP_INT64_TCP_SPEED = 20200;
159    private static final int FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION = 20300;
160    // ----------------------------------------
161    // source switch mode
162    // 普通模式,不支持无缝切换
163    public static final int  SOURCE_SWITCH_NORMAL_MODE = 0;
164    // MOV 无缝模式,支持mov,mp4,m4a,3gp,3g2,mj2媒体格式
165    public static final int SOURCE_SWITCH_SMOOTH_MODE = 1;
166    // HLS无缝模式
167    public static final int SOURCE_SWITCH_SMOOTH_HLS_MODE = 2;
168    // ----------------------------------------
169    private static final String SP_FILE_FOR_KEY = "__cyberplayer_dl_sec";
170
171    @AccessedByNative
172    private long mNativeMediaPlayer;
173    @AccessedByNative
174    private long mNativeMediaDataSource;
175
176    @AccessedByNative
177    private long mNativeAndroidIO;
178
179    @AccessedByNative
180    private int mNativeSurfaceTexture;
181
182    @AccessedByNative
183    private int mListenerContext;
184
185    private SurfaceHolder mSurfaceHolder;
186    private EventHandler mEventHandler;
187    private PowerManager.WakeLock mWakeLock = null;
188    private boolean mScreenOnWhilePlaying;
189    private boolean mStayAwake;
190
191    private int mVideoWidth;
192    private int mVideoHeight;
193    private int mVideoSarNum;
194    private int mVideoSarDen;
195
196    private String mDataSource;
197    private int mNetworkTimeoutInUs = 15000000;
198
199    private int stayInterval = 0;
200    private long lastStartPlayTime = 0L;
201    private JSONArray bufferStatJsonArray = new JSONArray();
202    private Date latestBufferStartTime = null;
203    private long bufferingStartPosition = -1;
204    private boolean mApmEventPlayerCreated = false;
205
206    private long startPrepareTimeForApm = 0L;
207    private int maxCacheSizeForApm = 0;
208    private int cachePauseTimeForApm = 0;
209    private int firstBufferingTimeForApm = 0;
210    private boolean toggleFrameChasingForApm;
211    private volatile boolean isEndSended = true;
212    private Timer apmPlayingTimer = null;
213
214    private Context appContext = null;
215
216    private String mCustomisedHeaders = null;
217
218
219    /**
220     * 设置Access Key
221     *
222     * @param akOfBDCloud 百度云后台的ak,详见 百度云后台 --> 右上角的用户名 --> "安全认证" --> "Access Key"
223     */
224    public static void setAK(String akOfBDCloud) {
225        mAK = akOfBDCloud;
226    }
227
228    public static void setCuid(String cuid) {
229        sCuid = cuid;
230        APMEventHandle.setCuid(cuid);
231    }
232
233    /**
234     * 设置是否开启P2P下载模块
235     *
236     * @param isEnable 是否开启
237     */
238    public static void setP2PEnabled(boolean isEnable) {
239        sEnableP2P = isEnable;
240    }
241
242    /**
243     * 设置是否开启本地缓存功能,开启该功能后,可以在观看过程中将音视频数据临时缓存到本地目录
244     *
245     * @param isEnable  是否开启
246     * @param cachePath 临时缓存路径,如果开启本地缓存功能,该路径不能为空
247     */
248    public static void setLocalCacheEnabled(boolean isEnable, String cachePath) {
249        sEnableCache = isEnable;
250        sCacheRootPath = cachePath;
251        if (sEnableCache && TextUtils.isEmpty(sCacheRootPath)) {
252            throw new IllegalArgumentException("cachePath can not be empty if enabled local cache");
253        }
254    }
255
256    /**
257     * Default library loader Load them by yourself, if your libraries are not installed at default place.
258     */
259    private static final BDCloudLibLoader sLocalLibLoader = new BDCloudLibLoader() {
260        @Override
261        public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException {
262            System.loadLibrary(libName);
263        }
264    };
265
266    private static volatile boolean mIsLibLoaded = false;
267
268    /**
269     * so库定制加载,一般不需要调用。因实例化BDCloudMediaPlayer时会自动加载so,若想定制加载,务必在创建player实例前调用。
270     *
271     * @param libLoader
272     */
273    public static void loadLibrariesOnce(BDCloudLibLoader libLoader) {
274        synchronized (BDCloudMediaPlayer.class) {
275            if (!mIsLibLoaded) {
276                if (libLoader == null) {
277                    libLoader = sLocalLibLoader;
278                }
279
280                try {
281                    libLoader.loadLibrary("pcdn");
282                } catch (Throwable e) { // In case p2p module is not enabled
283                    // no need to log this info
284                    // e.printStackTrace();
285                } finally {
286                    libLoader.loadLibrary("stlport_shared");
287                    libLoader.loadLibrary("bdsoundutils");
288                    libLoader.loadLibrary("bdplayer");
289                    mIsLibLoaded = true;
290                }
291            }
292        }
293    }
294
295    /**
296     * 默认构造方法
297     * <p>
298     * 当使用完BDCloudMediaPlayer之后,务必调用{@link #release()}以释放资源,否则过多的BDCloudMediaPlayer实例可能导致异常。
299     * </p>
300     */
301    public BDCloudMediaPlayer(Context context) {
302        this(context, sLocalLibLoader);
303    }
304
305    /**
306     * 构造方法
307     * <p>
308     * 当使用完BDCloudMediaPlayer之后,务必调用{@link #release()}以释放资源,否则过多的BDCloudMediaPlayer实例可能导致异常。
309     * </p>
310     *
311     * @param context   上下文
312     * @param libLoader 定制so加载器
313     */
314    public BDCloudMediaPlayer(Context context, BDCloudLibLoader libLoader) {
315        appContext = context.getApplicationContext();
316        // 禁止用户行为数据上报,用户行为后台暂不维护
317        APMEventHandle.setUAUploadEnable(false);
318        APMEventHandle.setCuid(sCuid);
319        mApmEventPlayerCreated = true;
320
321        initPlayer(libLoader);
322    }
323
324    private void initPlayer(BDCloudLibLoader libLoader) {
325        loadLibrariesOnce(libLoader);
326
327        Looper looper;
328        if ((looper = Looper.myLooper()) != null) {
329            mEventHandler = new EventHandler(this, looper);
330        } else if ((looper = Looper.getMainLooper()) != null) {
331            mEventHandler = new EventHandler(this, looper);
332        } else {
333            mEventHandler = null;
334        }
335
336        /*
337         * Native init requires a weak reference to our object. It's easier to create it here than in C++.
338         */
339        native_init(new WeakReference<BDCloudMediaPlayer>(this));
340
341        setDecodeMode(DECODE_AUTO);
342        setOption(OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
343        setOption(OPT_CATEGORY_PLAYER, "max-fps", 30);
344        setOption(OPT_CATEGORY_PLAYER, "framedrop", 1);
345        setOption(OPT_CATEGORY_PLAYER, "framechasing", 0);
346        setOption(OPT_CATEGORY_PLAYER, "soundtouch", 1);
347        setOption(OPT_CATEGORY_PLAYER, "subtitle", 1);
348        setOption(OPT_CATEGORY_PLAYER, "max-buffer-size", 15 * 1024 * 1024);
349        setOption(OPT_CATEGORY_PLAYER, "max-buffer-duration", 10 * 1000);
350        setOption(OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
351        setLogEnabled(false);
352    }
353
354    private void setupPlayer() {
355        // setOption(OPT_CATEGORY_FORMAT, "auto_convert", 0); // only for concat protocol
356        setOption(OPT_CATEGORY_FORMAT, "reconnect", 1);
357        setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs);
358        native_setup();
359    }
360
361    /*
362     * Update the BDCloudMediaPlayer SurfaceTexture. Call after setting a new display surface.
363     */
364    private native void _setVideoSurface(Surface surface);
365
366    /**
367     * 设置 {@link SurfaceHolder} 用于显示视频
368     * <p>
369     * 如果想显示视频,需要设置SurfaceHolder或者Surface。既不设置该方法也不设置{@link #setSurface(Surface)}方法,会导致仅有音频播放。将
370     * SurfaceHolder或者Surface设置为空,也会导致仅播放音频。
371     *
372     * @param sh the SurfaceHolder to use for video display
373     */
374    @Override
375    public void setDisplay(SurfaceHolder sh) {
376        mSurfaceHolder = sh;
377        Surface surface;
378        if (sh != null) {
379            surface = sh.getSurface();
380        } else {
381            surface = null;
382        }
383        _setVideoSurface(surface);
384        updateSurfaceScreenOn();
385    }
386
387    /**
388     * 设置{@link Surface}来显示视频,与接口{@link #setDisplay(SurfaceHolder)}功能类似,但不支持{@link #setScreenOnWhilePlaying(boolean)}
389     * 接口的设置。当调用该接口时,之前设置的Surface或SurfaceHolder将被替换。设置为null会仅播放音频。
390     * <p>
391     * 如果设置的Surface会往{@link SurfaceTexture}发送frames,{@link SurfaceTexture#getTimestamp()}接口返回的时间戳会有未指定的零点。这些时间戳
392     * 在不同媒体资源、同一资源的不同实例、同一程序的多次运行的情况中不具有可比性。这个时间戳会单调递增,并且不受时间调整的影响,但在位置设置后会被重置。
393     *
394     * @param surface The {@link Surface} to be used for the video portion of the media.
395     */
396    @Override
397    public void setSurface(Surface surface) {
398        if (mScreenOnWhilePlaying && surface != null) {
399            DebugLog.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
400        }
401        mSurfaceHolder = null;
402        _setVideoSurface(surface);
403        updateSurfaceScreenOn();
404    }
405
406    /**
407     * 设置播放源
408     *
409     * @param context the Context to use when resolving the Uri
410     * @param uri     the Content URI of the data you want to play
411     * @throws IllegalStateException if it is called in an invalid state
412     */
413    @Override
414    public void setDataSource(Context context, Uri uri)
415            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
416        setDataSource(context, uri, null);
417    }
418
419    /**
420     * 设置播放源,可设置请求头信息
421     *
422     * @param context the Context to use when resolving the Uri
423     * @param uri     the Content URI of the data you want to play
424     * @param headers the headers to be sent together with the request for the data Note that the cross domain
425     *                redirection is allowed by default, but that can be changed with key/value pairs through the headers
426     *                parameter with "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
427     *                disallow or allow cross domain redirection.
428     * @throws IllegalStateException if it is called in an invalid state
429     */
430    @Override
431    public void setDataSource(Context context, Uri uri, Map<String, String> headers)
432            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
433        final String scheme = uri.getScheme();
434        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
435            setDataSource(uri.getPath());
436            return;
437        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) && Settings.AUTHORITY.equals(uri.getAuthority())) {
438            // Redirect ringtones to go directly to underlying provider
439            uri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.getDefaultType(uri));
440            if (uri == null) {
441                throw new FileNotFoundException("Failed to resolve default ringtone");
442            }
443        }
444
445        AssetFileDescriptor fd = null;
446        try {
447            ContentResolver resolver = context.getContentResolver();
448            fd = resolver.openAssetFileDescriptor(uri, "r");
449            if (fd == null) {
450                return;
451            }
452            // Note: using getDeclaredLength so that our behavior is the same
453            // as previous versions when the content provider is returning
454            // a full file.
455            if (fd.getDeclaredLength() < 0) {
456                setDataSource(fd.getFileDescriptor());
457            } else {
458                setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
459            }
460            return;
461        } catch (SecurityException ignored) {
462        } catch (IOException ignored) {
463        } finally {
464            if (fd != null) {
465                fd.close();
466            }
467        }
468
469        setDataSource(uri.toString(), headers);
470    }
471
472    /**
473     * 设置播放源 (file-path or http/rtsp URL)
474     *
475     * @param path the path of the file, or the http/rtsp URL of the stream you want to play
476     * @throws IllegalStateException if it is called in an invalid state
477     *                               <p>
478     *                               <p>
479     *                               When <code>path</code> refers to a local file, the file may actually be opened by a process other
480     *                               than the calling application. This implies that the pathname should be an absolute path (as any other
481     *                               process runs with unspecified current working directory), and that the pathname should reference a
482     *                               world-readable file.
483     */
484    @Override
485    public void setDataSource(String path)
486            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
487        mDataSource = path;
488        this.onInitingStat();
489        setupPlayer();
490        _setDataSource(path, null, null);
491
492        if (isLocalFilePath(path)) {
493//            Log.d(TAG, "isLocalFile=" + path);
494            // try to fetch
495            String newFile = path.replace("file://", "");
496            SharedPreferences sp = appContext.getSharedPreferences(SP_FILE_FOR_KEY, 0);
497            String savedKey = sp.getString(newFile, null);
498            if (savedKey != null) {
499                try {
500                    String key = LocalHlsSec.decryptStr(appContext, savedKey);
501
502                    if (key != null && key.length() > 0) {
503                        this.setLocalDecryptKeyForHLS(key);
504                    }
505                } catch (Exception e) {
506                    Log.d(TAG, "", e);
507                }
508
509            }
510        } else {
511            setOption(OPT_CATEGORY_PLAYER, "enable_p2p", sEnableP2P ? 1 : 0);
512            setOption(OPT_CATEGORY_PLAYER, "enable_cache", sEnableCache ? 1 : 0);
513            setOption(OPT_CATEGORY_FORMAT, "cache_dir", sEnableCache ? sCacheRootPath : "");
514        }
515    }
516
517    /**
518     * 设置播放源 (file-path or http/rtsp URL),可设置网络请求头部信息。
519     *
520     * @param path    the path of the file, or the http/rtsp URL of the stream you want to play
521     * @param headers the headers associated with the http request for the stream you want to play
522     * @throws IllegalStateException if it is called in an invalid state
523     */
524    public void setDataSource(String path, Map<String, String> headers)
525            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
526        if (headers != null && !headers.isEmpty()) {
527            StringBuilder sb = new StringBuilder();
528            for (Map.Entry<String, String> entry : headers.entrySet()) {
529                sb.append(entry.getKey());
530                sb.append(": ");
531                String value = entry.getValue();
532                if (!TextUtils.isEmpty(value)) {
533                    sb.append(entry.getValue());
534                }
535                sb.append("\r\n");
536            }
537            mCustomisedHeaders = sb.toString();
538            setOption(OPT_CATEGORY_FORMAT, "headers", mCustomisedHeaders);
539        } else {
540            mCustomisedHeaders = null;
541        }
542
543        setDataSource(path);
544    }
545
546    /**
547     * 设置播放源 (FileDescriptor). It is the caller's responsibility to close the file descriptor. It
548     * is safe to do so as soon as this call returns.
549     *
550     * @param fd the FileDescriptor for the file you want to play
551     * @throws IllegalStateException if it is called in an invalid state
552     */
553    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
554    @Override
555    public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException {
556        setupPlayer();
557        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
558            int native_fd = -1;
559            try {
560                Field f = fd.getClass().getDeclaredField("descriptor"); // NoSuchFieldException
561                f.setAccessible(true);
562                native_fd = f.getInt(fd); // IllegalAccessException
563            } catch (NoSuchFieldException e) {
564                throw new RuntimeException(e);
565            } catch (IllegalAccessException e) {
566                throw new RuntimeException(e);
567            }
568            _setDataSourceFd(native_fd);
569        } else {
570            ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd);
571            try {
572                _setDataSourceFd(pfd.getFd());
573            } finally {
574                pfd.close();
575            }
576        }
577    }
578
579    /**
580     * 设置播放源 (FileDescriptor). The FileDescriptor must be seekable (N.B. a LocalSocket is not
581     * seekable). It is the caller's responsibility to close the file descriptor. It is safe to do so as soon as this
582     * call returns.
583     *
584     * @param fd     the FileDescriptor for the file you want to play
585     * @param offset the offset into the file where the data to be played starts, in bytes
586     * @param length the length in bytes of the data to be played
587     * @throws IllegalStateException if it is called in an invalid state
588     */
589    private void setDataSource(FileDescriptor fd, long offset, long length)
590            throws IOException, IllegalArgumentException, IllegalStateException {
591        // FIXME: handle offset, length
592        setDataSource(fd);
593    }
594
595    public void setDataSource(IMediaDataSource mediaDataSource)
596            throws IllegalArgumentException, SecurityException, IllegalStateException {
597        setupPlayer();
598        _setDataSource(mediaDataSource);
599    }
600
601    private native void _setDataSource(String path, String[] keys, String[] values)
602            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
603
604    private native void _setDataSourceFd(int fd)
605            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
606
607    private native void _setDataSource(IMediaDataSource mediaDataSource)
608            throws IllegalArgumentException, SecurityException, IllegalStateException;
609
610    /**
611     * 获取播放路径
612     *
613     * @return
614     */
615    @Override
616    public String getDataSource() {
617        return mDataSource;
618    }
619
620    /**
621     * 异步准备,播放器仅支持异步准备。您可以在准备好后调用start来启动播放
622     *
623     * @throws IllegalStateException
624     */
625    @Override
626    public void prepareAsync() throws IllegalStateException {
627        _prepareAsync();
628    }
629
630    private native void _prepareAsync() throws IllegalStateException;
631
632    /**
633     * 启动播放
634     * 要求播放源已经准备好
635     *
636     * @throws IllegalStateException
637     */
638    @Override
639    public void start() throws IllegalStateException {
640        stayAwake(true);
641        this.onResumeStat();
642        _start();
643    }
644
645    private native void _start() throws IllegalStateException;
646
647    /**
648     * 停止播放
649     *
650     * @throws IllegalStateException
651     */
652    @Override
653    public void stop() throws IllegalStateException {
654        stayAwake(false);
655        this.onStopStat(0, 0);
656        this.onEndStat();
657        _stop();
658    }
659
660    private native void _stop() throws IllegalStateException;
661
662    /**
663     * 暂停播放
664     *
665     * @throws IllegalStateException
666     */
667    @Override
668    public void pause() throws IllegalStateException {
669        stayAwake(false);
670        this.onPauseStat();
671        _pause();
672    }
673
674    private native void _pause() throws IllegalStateException;
675
676    /**
677     * 设置保持唤醒模式
678     *
679     * @param context
680     * @param mode    该模式为PowerManager.newWakeLock的接口参数
681     */
682    @SuppressLint("Wakelock")
683    @Override
684    public void setWakeMode(Context context, int mode) {
685        boolean washeld = false;
686        if (mWakeLock != null) {
687            if (mWakeLock.isHeld()) {
688                washeld = true;
689                mWakeLock.release();
690            }
691            mWakeLock = null;
692        }
693
694        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
695        mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, BDCloudMediaPlayer.class.getName());
696        mWakeLock.setReferenceCounted(false);
697        if (washeld) {
698            mWakeLock.acquire();
699        }
700    }
701
702    /**
703     * 设置播放时屏幕保持,仅在设置过SurfaceHolder时有效。
704     *
705     * @param screenOn
706     */
707    @Override
708    public void setScreenOnWhilePlaying(boolean screenOn) {
709        if (mScreenOnWhilePlaying != screenOn) {
710            if (screenOn && mSurfaceHolder == null) {
711                DebugLog.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
712            }
713            mScreenOnWhilePlaying = screenOn;
714            updateSurfaceScreenOn();
715        }
716    }
717
718    @SuppressLint("Wakelock")
719    private void stayAwake(boolean awake) {
720        if (mWakeLock != null) {
721            if (awake && !mWakeLock.isHeld()) {
722                mWakeLock.acquire();
723            } else if (!awake && mWakeLock.isHeld()) {
724                mWakeLock.release();
725            }
726        }
727        mStayAwake = awake;
728        updateSurfaceScreenOn();
729    }
730
731    private void updateSurfaceScreenOn() {
732        if (mSurfaceHolder != null) {
733            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
734        }
735    }
736
737    /**
738     * 获取音视频track的信息
739     *
740     * @return
741     */
742    @Override
743    public BDCloudTrackInfo[] getTrackInfo() {
744        Bundle bundle = getMediaMeta();
745        if (bundle == null) {
746            return null;
747        }
748
749        BDCloudMediaMeta mediaMeta = BDCloudMediaMeta.parse(bundle);
750        if (mediaMeta == null || mediaMeta.mStreams == null) {
751            return null;
752        }
753
754        ArrayList<BDCloudTrackInfo> trackInfos = new ArrayList<BDCloudTrackInfo>();
755        for (BDCloudMediaMeta.BDCloudStreamMeta streamMeta : mediaMeta.mStreams) {
756            BDCloudTrackInfo trackInfo = new BDCloudTrackInfo(streamMeta);
757            if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__VIDEO)) {
758                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_VIDEO);
759            } else if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__AUDIO)) {
760                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO);
761            } else if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__TIMEDTEXT)) {
762                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
763            }
764            trackInfos.add(trackInfo);
765        }
766
767        return trackInfos.toArray(new BDCloudTrackInfo[trackInfos.size()]);
768    }
769
770    // TODO: @Override
771    public int getSelectedTrack(int trackType) {
772        switch (trackType) {
773            case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO:
774                return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_VIDEO_STREAM, -1);
775            case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO:
776                return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_AUDIO_STREAM, -1);
777            case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT:
778                return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM, -1);
779            default:
780                return -1;
781        }
782    }
783
784    // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
785    // TODO: @Override
786    public void selectTrack(int track) {
787        _setStreamSelected(track, true);
788    }
789
790    // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
791    // TODO: @Override
792    public void deselectTrack(int track) {
793        _setStreamSelected(track, false);
794    }
795
796    private native void _setStreamSelected(int stream, boolean select);
797
798    /**
799     * 获取视频宽度
800     *
801     * @return
802     */
803    @Override
804    public int getVideoWidth() {
805        return mVideoWidth;
806    }
807
808    /**
809     * 获取视频高度
810     *
811     * @return
812     */
813    @Override
814    public int getVideoHeight() {
815        return mVideoHeight;
816    }
817
818    /**
819     * 获取采样纵横比的分子
820     *
821     * @return
822     */
823    @Override
824    public int getVideoSarNum() {
825        return mVideoSarNum;
826    }
827
828    /**
829     * 获取采样纵横比的分母
830     *
831     * @return
832     */
833    @Override
834    public int getVideoSarDen() {
835        return mVideoSarDen;
836    }
837
838    /**
839     * 是否正在播放
840     *
841     * @return
842     */
843    @Override
844    public native boolean isPlaying();
845
846    /**
847     * 快速切换到某个时间点进行播放
848     *
849     * @param msec
850     * @throws IllegalStateException
851     */
852    @Override
853    public void seekTo(long msec) throws IllegalStateException {
854        this.onSeekToStat(this.getCurrentPosition() / 1000, msec / 1000);
855        _seekTo(msec);
856    }
857
858    private native void _seekTo(long msec) throws IllegalStateException;
859
860    /**
861     * 获取当前播放位置,单位为毫秒
862     *
863     * @return
864     */
865    @Override
866    public native long getCurrentPosition();
867
868    /**
869     * 获取音视频时长,单位为毫秒
870     *
871     * @return
872     */
873    @Override
874    public native long getDuration();
875
876    /**
877     * 释放资源
878     * Releases resources associated with this BDCloudMediaPlayer object. It is considered good practice to call this
879     * method when you're done using the BDCloudMediaPlayer. In particular, whenever an Activity of an application is
880     * paused (its onPause() method is called), or stopped (its onStop() method is called), this method should be
881     * invoked to release the BDCloudMediaPlayer object, unless the application has a special need to keep the object
882     * around. In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to call
883     * this method immediately if a BDCloudMediaPlayer object is no longer needed may also lead to continuous battery
884     * consumption for mobile devices, and playback failure for other applications if no multiple instances of the same
885     * codec are supported on a device. Even if multiple instances of the same codec are supported, some performance
886     * degradation may be expected when unnecessary multiple instances are used at the same time.
887     */
888    @Override
889    public void release() {
890        stayAwake(false);
891        this.onEndStat();
892        updateSurfaceScreenOn();
893        resetListeners();
894        APMEventHandle.getInstance(appContext).release();
895        _release();
896    }
897
898    private native void _release();
899
900    /**
901     * 重置,将状态重置为IDLE
902     * 重置后需重新设置播放源
903     */
904    @Override
905    public void reset() {
906        stayAwake(false);
907        this.onEndStat();
908        _stop();
909        _reset();
910        // make sure none of the listeners get called anymore
911        mEventHandler.removeCallbacksAndMessages(null);
912
913        mVideoWidth = 0;
914        mVideoHeight = 0;
915    }
916
917    private native void _reset();
918
919    /**
920     * 设置是否循环播放
921     *
922     * @param looping whether to loop or not
923     */
924    @Override
925    public void setLooping(boolean looping) {
926        int loopCount = looping ? 0 : 1;
927        setOption(OPT_CATEGORY_PLAYER, "loop", loopCount);
928        _setLoopCount(loopCount);
929    }
930
931    private native void _setLoopCount(int loopCount);
932
933    /**
934     * 是否循环播放
935     *
936     * @return true if the MediaPlayer is currently looping, false otherwise
937     */
938    @Override
939    public boolean isLooping() {
940        int loopCount = _getLoopCount();
941        return loopCount != 1;
942    }
943
944    private native int _getLoopCount();
945
946    /**
947     * 设置播放速度,目前仅支持Android 6.0及以上版本
948     *
949     * @param speed
950     */
951    @TargetApi(Build.VERSION_CODES.M)
952    public void setSpeed(float speed) {
953        if (speed == 0) {
954            return;
955        }
956        _setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed);
957    }
958
959    /**
960     * 获取播放速度,目前仅支持Android 6.0及以上版本
961     *
962     * @param speed
963     * @return
964     */
965    @TargetApi(Build.VERSION_CODES.M)
966    public float getSpeed(float speed) {
967        return _getPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, .0f);
968    }
969
970    /**
971     * 获取当前视频的decoder类型,1为软解;2为硬解
972     *
973     * @return
974     */
975    public int getVideoDecoder() {
976        return (int) _getPropertyLong(FFP_PROP_INT64_VIDEO_DECODER, FFP_PROPV_DECODER_UNKNOWN);
977    }
978
979    /**
980     * 获取视频输出帧率
981     *
982     * @return
983     */
984    public float getVideoOutputFramesPerSecond() {
985        return _getPropertyFloat(PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND, 0.0f);
986    }
987
988    /**
989     * 获取视频解码帧率
990     *
991     * @return
992     */
993    public float getVideoDecodeFramesPerSecond() {
994        return _getPropertyFloat(PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND, 0.0f);
995    }
996
997    /**
998     * 获取视频已缓冲好的长度
999     *
1000     * @return
1001     */
1002    public long getVideoCachedDuration() {
1003        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_DURATION, 0);
1004    }
1005
1006    /**
1007     * 获取音频已缓冲好的长度
1008     *
1009     * @return
1010     */
1011    public long getAudioCachedDuration() {
1012        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_DURATION, 0);
1013    }
1014
1015    /**
1016     * 获取视频已缓冲好的字节数
1017     *
1018     * @return
1019     */
1020    public long getVideoCachedBytes() {
1021        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_BYTES, 0);
1022    }
1023
1024    /**
1025     * 获取音频已缓冲好的字节数
1026     *
1027     * @return
1028     */
1029    public long getAudioCachedBytes() {
1030        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_BYTES, 0);
1031    }
1032
1033    /**
1034     * 获取视频已缓冲好的包数
1035     *
1036     * @return
1037     */
1038    public long getVideoCachedPackets() {
1039        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_PACKETS, 0);
1040    }
1041
1042    /**
1043     * 获取音频已缓冲好的包数
1044     *
1045     * @return
1046     */
1047    public long getAudioCachedPackets() {
1048        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_PACKETS, 0);
1049    }
1050
1051    public long getAsyncStatisticBufBackwards() {
1052        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS, 0);
1053    }
1054
1055    public long getAsyncStatisticBufForwards() {
1056        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS, 0);
1057    }
1058
1059    public long getAsyncStatisticBufCapacity() {
1060        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY, 0);
1061    }
1062
1063    public long getTrafficStatisticByteCount() {
1064        return _getPropertyLong(FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT, 0);
1065    }
1066
1067    public long getCacheStatisticPhysicalPos() {
1068        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS, 0);
1069    }
1070
1071    public long getCacheStatisticBufForwards() {
1072        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS, 0);
1073    }
1074
1075    public long getCacheStatisticFilePos() {
1076        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS, 0);
1077    }
1078
1079    public long getCacheStatisticCountBytes() {
1080        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES, 0);
1081    }
1082
1083    public long getBitRate() {
1084        return _getPropertyLong(FFP_PROP_INT64_BIT_RATE, 0);
1085    }
1086
1087    /**
1088     * 获取网络下载速度
1089     *
1090     * @return
1091     */
1092    public long getDownloadSpeed() {
1093        return _getPropertyLong(FFP_PROP_INT64_TCP_SPEED, 0);
1094    }
1095
1096    public long getSeekLoadDuration() {
1097        return _getPropertyLong(FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION, 0);
1098    }
1099
1100    private native float _getPropertyFloat(int property, float defaultValue);
1101
1102    private native void _setPropertyFloat(int property, float value);
1103
1104    private native long _getPropertyLong(int property, long defaultValue);
1105
1106    private native void _setPropertyLong(int property, long value);
1107
1108    /**
1109     * 设置左右声道的音量
1110     *
1111     * @param leftVolume
1112     * @param rightVolume
1113     */
1114    @Override
1115    public native void setVolume(float leftVolume, float rightVolume);
1116
1117    @Override
1118    public native int getAudioSessionId();
1119
1120
1121    /**
1122     * 获取sdk版本,形式为 xx.xx.xx
1123     *
1124     * @return
1125     */
1126    public static String getSdkVersion() {
1127        return SDK_VERSION;
1128    }
1129
1130    /**
1131     * 获取媒体信息
1132     * 包含解码信息与音视频流信息
1133     *
1134     * @return
1135     */
1136    @Override
1137    public MediaInfo getMediaInfo() {
1138        MediaInfo mediaInfo = new MediaInfo();
1139        mediaInfo.mMediaPlayerName = "bdcloudplayer";
1140
1141        String videoCodecInfo = _getVideoCodecInfo();
1142        if (!TextUtils.isEmpty(videoCodecInfo)) {
1143            String nodes[] = videoCodecInfo.split(",");
1144            if (nodes.length >= 2) {
1145                mediaInfo.mVideoDecoder = nodes[0];
1146                mediaInfo.mVideoDecoderImpl = nodes[1];
1147            } else if (nodes.length >= 1) {
1148                mediaInfo.mVideoDecoder = nodes[0];
1149                mediaInfo.mVideoDecoderImpl = "";
1150            }
1151        }
1152
1153        String audioCodecInfo = _getAudioCodecInfo();
1154        if (!TextUtils.isEmpty(audioCodecInfo)) {
1155            String nodes[] = audioCodecInfo.split(",");
1156            if (nodes.length >= 2) {
1157                mediaInfo.mAudioDecoder = nodes[0];
1158                mediaInfo.mAudioDecoderImpl = nodes[1];
1159            } else if (nodes.length >= 1) {
1160                mediaInfo.mAudioDecoder = nodes[0];
1161                mediaInfo.mAudioDecoderImpl = "";
1162            }
1163        }
1164
1165        try {
1166            mediaInfo.mMeta = BDCloudMediaMeta.parse(_getMediaMeta());
1167        } catch (Throwable e) {
1168            e.printStackTrace();
1169        }
1170        return mediaInfo;
1171    }
1172
1173
1174    public void setMediaInputType(int mode) {
1175        switch (mode) {
1176            case SOURCE_SWITCH_SMOOTH_MODE:
1177                setOption(OPT_CATEGORY_PLAYER,"iformat","adapt_media");
1178                setOption(OPT_CATEGORY_FORMAT,"get_master_plist",0);
1179                break;
1180            case SOURCE_SWITCH_SMOOTH_HLS_MODE:
1181                setOption(OPT_CATEGORY_PLAYER,"iformat","adapt_media");
1182                setOption(OPT_CATEGORY_FORMAT,"get_master_plist",1);
1183                break;
1184            case SOURCE_SWITCH_NORMAL_MODE:
1185                setOption(OPT_CATEGORY_PLAYER,"iformat",null);
1186                break;
1187                default:
1188                    setOption(OPT_CATEGORY_PLAYER,"iformat",null);
1189                    break;
1190        }
1191    }
1192
1193    /**
1194     * 切换多分辨率
1195     * 需要首先调用getVariantInfo拿到数组
1196     *
1197     * @param index VariantInfo数组的下标。
1198     * @return
1199     */
1200    public boolean selectResolutionByIndex(int index) {
1201        // the same time
1202        if (getCurrentVariantIndex() == index) {
1203            Log.d(TAG, "currentVariantIndex is equals to index setted~" + index);
1204            return false;
1205        } else if (index < 0 || index >= this.getVariantInfo().length) {
1206            Log.d(TAG, "index is not in [0," + this.getVariantInfo().length + ")");
1207            return false;
1208        }
1209        long currentPosition = this.getCurrentPosition();
1210        this.stop();
1211        native_setup();
1212        // option of category[OPT_CATEGORY_FORMAT] should be restored before preparing
1213        // setOption(OPT_CATEGORY_FORMAT, "auto_convert", 0); // for concat protocol only
1214        setOption(OPT_CATEGORY_FORMAT, "reconnect", 1);
1215        setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs);
1216        setOption(OPT_CATEGORY_FORMAT, "cache_dir", sEnableCache ? sCacheRootPath : "");
1217        if (!TextUtils.isEmpty(mCustomisedHeaders)) {
1218            setOption(OPT_CATEGORY_FORMAT, "headers", mCustomisedHeaders);
1219        }
1220        this.selectVariantByIndex(index);
1221        setInitPlayPosition(currentPosition);
1222        this.prepareAsync();
1223        return true;
1224    }
1225
1226    public boolean selectMediaByIndex(int index) {
1227        if (getCurrentMediaIndex() == index) {
1228            Log.d(TAG, "Is the same index :" + index);
1229            return false;
1230        } else if (index < 0 || index >= this.getMediaItems().length) {
1231            Log.d(TAG, "index is not in [0," + this.getMediaItems().length + ")");
1232            return false;
1233        }
1234        return setMediaItemIndex(index);
1235    }
1236
1237    /**
1238     * 设置初始播放位置, 需在prepareAsync之前调用
1239     *
1240     * @param positionInMilliSeconds
1241     */
1242    public void setInitPlayPosition(long positionInMilliSeconds) {
1243        this.setOption(OPT_CATEGORY_PLAYER, "seek-at-start", positionInMilliSeconds);
1244    }
1245
1246    /**
1247     * 默认:优先硬解,无法硬解时软解
1248     */
1249    public static final int DECODE_AUTO = 0;
1250    /**
1251     * 设置为软解
1252     */
1253    public static final int DECODE_SW = 1;
1254
1255    private int decodeMode = DECODE_AUTO;
1256
1257    /**
1258     * 设置软硬解模式,默认为auto模式(自动检测,优先硬解)
1259     *
1260     * @param mode 取值为DECODE_AUTO或DECODE_SW
1261     */
1262    public void setDecodeMode(int mode) {
1263        if (mode >= 0 && mode <= 1) {
1264            decodeMode = mode;
1265            if (decodeMode == DECODE_AUTO) {
1266                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
1267                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-all-videos", 1);
1268                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
1269                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER,
1270                        "mediacodec-handle-resolution-change", 1);
1271            } else {
1272                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
1273                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-all-videos", 0);
1274                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);
1275                this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER,
1276                        "mediacodec-handle-resolution-change", 0);
1277            }
1278        } else {
1279            DebugLog.e(TAG, "decodeMode shoule be DECODE_AUTO or DECODE_SW");
1280        }
1281    }
1282
1283    /**
1284     * 获取之前设置的解码模式
1285     *
1286     * @return
1287     */
1288    public int getDecodeMode() {
1289        return decodeMode;
1290    }
1291
1292//    /**
1293//     * 设置是否为直播地址,播放器会自动优化配置。
1294//     * @param isLiveUrl true表示为直播链接;false表示非直播链接
1295//     */
1296//    public void setIsLiveUrl(boolean isLiveUrl) {
1297//        if (isLiveUrl) {
1298//            // infbuf在底层的逻辑有变
1299//            setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
1300//            setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
1301//        } else {
1302//            setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 0);
1303//            setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 1);
1304//        }
1305//
1306//    }
1307
1308    private native String getCdnIp();
1309
1310    /**
1311     * 获得当前多码率视频的index
1312     *
1313     * @return
1314     */
1315    public native int getCurrentVariantIndex();
1316
1317    /**
1318     * 获得多码率无缝切换模式下当前的媒体index
1319     *
1320     * @return
1321     */
1322    public native int getCurrentMediaIndex();
1323
1324    public native boolean setMediaItemIndex(int index);
1325
1326    /**
1327     * 设置多码率index。特注:播放过程中设置多码率请使用selectResolutionByIndex,该函数会帮您处理播放器状态。
1328     *
1329     * @param index
1330     */
1331    public native void selectVariantByIndex(int index);
1332
1333    /**
1334     * DRM加密相关,一般不需设置
1335     *
1336     * @param id
1337     */
1338    public native void setCustomizedPlayerIdForHLS(String id);
1339
1340    /**
1341     * DRM加密相关,一般不需设置
1342     *
1343     * @param key
1344     */
1345    public native void setCustomizedPlayerKeyForHLS(String key);
1346
1347    /**
1348     * DRM加密-token加密,通过该接口设置token
1349     *
1350     * @param token
1351     */
1352    public native void setDecryptTokenForHLS(String token);
1353
1354    protected native void setLocalDecryptKeyForHLS(String key);
1355
1356    /**
1357     * 设置 最大缓冲区持续时间
1358     *
1359     * @param duration 单位 ms
1360     */
1361    public void setMaxCacheDuration(int duration){
1362        setOption(OPT_CATEGORY_PLAYER, "max-buffer-duration", duration);
1363    }
1364
1365    /**
1366     * 设置 最大缓冲区长度
1367     *
1368     * @param size
1369     */
1370    public void setMaxCacheSizeInBytes(int size) {
1371        maxCacheSizeForApm = size;
1372        this.setOption(OPT_CATEGORY_PLAYER, "max-buffer-size", size);
1373    }
1374
1375    /**
1376     * 设置 缓冲过程中,起播数据时长
1377     *
1378     * @param time
1379     */
1380    public void setBufferTimeInMs(int time) {
1381        cachePauseTimeForApm = time;
1382        this.setOption(OPT_CATEGORY_PLAYER, "buffer-time-in-ms", time);
1383    }
1384
1385    /**
1386     * 设置 缓冲过程中,起播数据字节长度
1387     *
1388     * @param size
1389     */
1390    public void setBufferSizeInBytes(int size) {
1391        this.setOption(OPT_CATEGORY_PLAYER, "buffer-size-in-bytes", size);
1392    }
1393
1394    /**
1395     * 设置probe(音视频格式探测)最大时长,单位是毫秒
1396     *
1397     * @param maxProbeTime
1398     */
1399    public void setMaxProbeTime(int maxProbeTime) {
1400        firstBufferingTimeForApm = maxProbeTime;
1401        this.setOption(OPT_CATEGORY_PLAYER, "max-probe-time", maxProbeTime);
1402    }
1403
1404    /**
1405     * 设置probe(音视频格式探测)最大数据大小
1406     *
1407     * @param maxProbeSize
1408     */
1409    public void setMaxProbeSize(int maxProbeSize) {
1410        this.setOption(OPT_CATEGORY_PLAYER, "max-probe-size", maxProbeSize);
1411    }
1412
1413    /**
1414     * Setting timeout threshold of network connecting and reading process
1415     *
1416     * @param timeout timeout in us
1417     */
1418    public void setTimeoutInUs(int timeout) {
1419        mNetworkTimeoutInUs = timeout > 0 ? timeout : mNetworkTimeoutInUs;
1420        this.setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs);
1421    }
1422
1423    /**
1424     * 设置是否开启追帧播放功能
1425     *
1426     * @param isEnable 是否开启
1427     */
1428    public void toggleFrameChasing(boolean isEnable) {
1429        toggleFrameChasingForApm = isEnable;
1430        this.setOption(OPT_CATEGORY_PLAYER, "framechasing", isEnable ? 1 : 0);
1431    }
1432
1433    public native void setMediaItems(String[] mediaItems);
1434
1435    /**
1436     * 获得多码率视频的各码率信息
1437     *
1438     * @return
1439     */
1440    public native String[] getVariantInfo();
1441
1442    public native String[] getMediaItems();
1443
1444    /**
1445     * 是否显示debug消息
1446     *
1447     * @param enable 当为false时,将仅显示warn级别及以上的消息;
1448     */
1449    @Override
1450    public void setLogEnabled(boolean enable) {
1451
1452        if (enable) {
1453            native_setLogLevel(IJK_LOG_DEFAULT);
1454            DebugLog.setDebugLogEnabled(true);
1455        } else {
1456            native_setLogLevel(IJK_LOG_WARN);
1457            DebugLog.setDebugLogEnabled(false);
1458        }
1459    }
1460
1461    /**
1462     * 是否可播放,当前始终为true
1463     *
1464     * @return
1465     */
1466    @Override
1467    public boolean isPlayable() {
1468        return true;
1469    }
1470
1471    private native String _getVideoCodecInfo();
1472
1473    private native String _getAudioCodecInfo();
1474
1475    /**
1476     * 设置额外信息,一般不需设置
1477     *
1478     * @param category
1479     * @param name
1480     * @param value
1481     */
1482    public void setOption(int category, String name, String value) {
1483        _setOption(category, name, value);
1484    }
1485
1486    /**
1487     * 设置额外信息,一般不需设置
1488     *
1489     * @param category
1490     * @param name
1491     * @param value
1492     */
1493    public void setOption(int category, String name, long value) {
1494        _setOption(category, name, value);
1495    }
1496
1497    private native void _setOption(int category, String name, String value);
1498
1499    private native void _setOption(int category, String name, long value);
1500
1501    public Bundle getMediaMeta() {
1502        return _getMediaMeta();
1503    }
1504
1505    private native Bundle _getMediaMeta();
1506
1507    public static String getColorFormatName(int mediaCodecColorFormat) {
1508        return _getColorFormatName(mediaCodecColorFormat);
1509    }
1510
1511    private static native String _getColorFormatName(int mediaCodecColorFormat);
1512
1513    /**
1514     * 当前该设置不生效
1515     *
1516     * @param streamtype
1517     */
1518    @Override
1519    public void setAudioStreamType(int streamtype) {
1520        // do nothing
1521    }
1522
1523    /**
1524     * 当前该设置不生效
1525     *
1526     * @param keepInBackground
1527     */
1528    @Override
1529    public void setKeepInBackground(boolean keepInBackground) {
1530        // do nothing
1531    }
1532
1533    private native void native_init(Object BDCloudMediaPlayer_this);
1534
1535    private native void native_setup();
1536
1537    private native void native_finalize();
1538
1539    private native void native_message_loop(Object BDCloudMediaPlayer_this);
1540
1541    protected void finalize() throws Throwable {
1542        super.finalize();
1543        native_finalize();
1544    }
1545
1546    protected void onErrorStat(int what, int extra) {
1547        try {
1548            APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 1);
1549        } catch (Exception e) {
1550            Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1551        }
1552    }
1553
1554    protected void onCompleteStat(int what, int extra) {
1555        APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 2);
1556    }
1557
1558    protected void onStopStat(int what, int extra) {
1559        APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 3);
1560    }
1561
1562    /**
1563     * when play completeOronErrorOrStopOrRelease mediaplayer, 但不能多发
1564     */
1565    protected void onEndStat() {
1566        // deal with complete/stop/reset/release multi Come In
1567        if (isEndSended) {
1568            return;
1569        }
1570        isEndSended = true;
1571        // add rest statis
1572        try {
1573            String strBufferStat = null;
1574            if (bufferStatJsonArray != null) {
1575                strBufferStat = bufferStatJsonArray.toString();
1576                bufferStatJsonArray = new JSONArray();
1577            }
1578
1579            if (apmPlayingTimer != null) {
1580                apmPlayingTimer.cancel();
1581                apmPlayingTimer = null;
1582            }
1583
1584            if (lastStartPlayTime != 0) {
1585                int interval = (int) (System.currentTimeMillis() - lastStartPlayTime) / 1000;
1586                lastStartPlayTime = 0;
1587                this.stayInterval += interval;
1588            }
1589            int playInterval = this.stayInterval;
1590            JSONObject extra = new JSONObject();
1591            extra.put("playInterval", playInterval);
1592            extra.put("buffering", new JSONArray(strBufferStat));
1593            APMEventHandle.getInstance(appContext).onUserPlayEnd(extra);
1594            this.stayInterval = 0;
1595        } catch (Exception e) {
1596            Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1597        }
1598
1599    }
1600
1601//    /**
1602//     * stop or release?
1603//     */
1604//    protected void onStopStat() {
1605//        if (apmPlayingTimer!= null) {
1606//            apmPlayingTimer.cancel();
1607//            apmPlayingTimer = null;
1608//        }
1609//    }
1610
1611    protected void onInitingStat() {
1612        lastStartPlayTime = System.currentTimeMillis();
1613        // start a session
1614        startPrepareTimeForApm = System.currentTimeMillis();
1615        APMEventHandle.getInstance(appContext).updateSession(this.mDataSource);
1616        APMEventHandle.getInstance(appContext).setPlayerRelated(SDK_VERSION, "hw", mAK);
1617        if (mApmEventPlayerCreated) {
1618            APMEventHandle.getInstance(appContext).onPlayerCreated();
1619            mApmEventPlayerCreated = false;
1620        }
1621    }
1622
1623    protected void onDnsStat(int duration) {
1624        APMEventHandle.getInstance(appContext).onDnsParsed(duration);
1625    }
1626
1627    protected void onHttpStat(long duration) {
1628        APMEventHandle.getInstance(appContext).onHttpConnected(duration / 1000);
1629    }
1630
1631    protected void onPauseStat() {
1632        if (lastStartPlayTime != 0) {
1633            int interval = (int) (System.currentTimeMillis() - lastStartPlayTime) / 1000;
1634            lastStartPlayTime = 0;
1635            this.stayInterval += interval;
1636        }
1637        // add rest statis
1638        try {
1639            if (apmPlayingTimer != null) {
1640                apmPlayingTimer.cancel();
1641                apmPlayingTimer = null;
1642            }
1643            APMEventHandle.getInstance(appContext).onUserPause(getCurrentPosition() / 1000f);
1644        } catch (Exception e) {
1645            Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1646        }
1647    }
1648
1649    protected void onResumeStat() {
1650        isEndSended = false;
1651        if (lastStartPlayTime == 0L) {
1652            lastStartPlayTime = System.currentTimeMillis();
1653        }
1654        try {
1655            if (apmPlayingTimer != null) {
1656                apmPlayingTimer.cancel();
1657                apmPlayingTimer = null;
1658            }
1659            apmPlayingTimer = new Timer();
1660            apmPlayingTimer.schedule(new TimerTask() {
1661                @Override
1662                public void run() {
1663                    try {
1664                        APMEventHandle.getInstance(appContext).onPlayCount();
1665                        long speed = getDownloadSpeed();
1666                        if (speed > 0) {
1667                            APMEventHandle.getInstance(appContext).onNetworkSpeedReport((int) speed);
1668                        }
1669                    } catch (Exception e) {
1670                        Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1671                    }
1672                }
1673            }, 0, 60 * 1000);
1674        } catch (Exception e) {
1675            Log.d(TAG, "" + e.getMessage());
1676        }
1677    }
1678
1679    protected void onSeekToStat(long curPositionInSeconds, long seekToInSeconds) {
1680        // add rest statis
1681        try {
1682            JSONObject extra = new JSONObject();
1683            extra.put("from", curPositionInSeconds);
1684            extra.put("to", seekToInSeconds);
1685            APMEventHandle.getInstance(appContext).onUserSeek(extra);
1686        } catch (Exception e) {
1687            Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1688        }
1689    }
1690
1691    protected void onPreparedStat() {
1692        // add rest statis
1693        try {
1694            if (startPrepareTimeForApm > 0L) {
1695                APMEventHandle.getInstance(appContext).onFirstBufferEnd((long) (System.currentTimeMillis()
1696                        - startPrepareTimeForApm));
1697                startPrepareTimeForApm = 0L;
1698            }
1699            // get cdn-ip
1700            String cdnIp = this.getCdnIp();
1701            if (cdnIp != null && !cdnIp.equals("")) {
1702                APMEventHandle.getInstance(appContext).onUpdateCdn(cdnIp);
1703            }
1704            JSONObject extra = new JSONObject();
1705            extra.put("videoUrl", this.mDataSource);
1706            extra.put("videoWidth", this.getVideoWidth());
1707            extra.put("videoHeight", this.getVideoHeight());
1708            extra.put("playerWidth", 0); // param is deprecated;new sdk is target for mediaplayer, not view
1709            extra.put("playerHeight", 0); // param is deprecated;new sdk is target for mediaplayer, not view
1710            extra.put("duration", this.getDuration());
1711            JSONObject settings = new JSONObject();
1712            settings.put("maxCacheSize", maxCacheSizeForApm);
1713            settings.put("cachePauseTime", cachePauseTimeForApm);
1714            settings.put("firstBufferingTime", firstBufferingTimeForApm);
1715            settings.put("toggleFrameChasing", toggleFrameChasingForApm);
1716            settings.put("decodeMode", decodeMode);
1717            settings.put("playbackRate", getSpeed(0f));
1718            settings.put("enableLooping", isLooping());
1719            extra.put("settings", settings);
1720            APMEventHandle.getInstance(appContext).onUserPlay(extra);
1721        } catch (Exception e) {
1722            Log.d(TAG, "APMEventHandle exception:" + e.getMessage());
1723        }
1724    }
1725
1726    protected void onMetadataStat(String playId) {
1727        APMEventHandle.getInstance(appContext).onUpdateMetadata(playId);
1728    }
1729
1730    protected void onInfoStat(int what, int extra) {
1731        try {
1732            if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) {
1733                latestBufferStartTime = new Date();
1734                bufferingStartPosition = getCurrentPosition();
1735
1736                APMEventHandle.getInstance(appContext).onBufferingStart();
1737            } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) {
1738                APMEventHandle.getInstance(appContext).onBufferingEnd();
1739
1740                if (this.latestBufferStartTime != null) {
1741                    Date endTime = new Date();
1742                    JSONObject json = new JSONObject();
1743                    json.put("startPosition", bufferingStartPosition / 1000f);
1744                    json.put("endPosition", getCurrentPosition() / 1000f);
1745                    json.put("startTimestamp", latestBufferStartTime.getTime());
1746                    json.put("endTimestamp", endTime.getTime());
1747                    bufferStatJsonArray.put(json);
1748                    latestBufferStartTime = null;
1749                    bufferingStartPosition = -1;
1750                }
1751            } else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
1752                APMEventHandle.getInstance(appContext).onFirstFrameRendered(lastStartPlayTime);
1753            } else if (what == IMediaPlayer.MEDIA_INFO_FRAMECHASING_START) {
1754                APMEventHandle.getInstance(appContext).onFrameChasingStart();
1755            } else if (what == IMediaPlayer.MEDIA_INFO_FRAMECHASING_END) {
1756                APMEventHandle.getInstance(appContext).onFrameCharingEnd();
1757            }
1758        } catch (Exception e) {
1759            Log.d(TAG, "buffer stat exception :" + e.getMessage());
1760        }
1761    }
1762
1763    private static class EventHandler extends Handler {
1764        private final WeakReference<BDCloudMediaPlayer> mWeakPlayer;
1765
1766        public EventHandler(BDCloudMediaPlayer mp, Looper looper) {
1767            super(looper);
1768            mWeakPlayer = new WeakReference<BDCloudMediaPlayer>(mp);
1769        }
1770
1771        @Override
1772        public void handleMessage(Message msg) {
1773            BDCloudMediaPlayer player = mWeakPlayer.get();
1774            if (player == null || player.mNativeMediaPlayer == 0) {
1775                DebugLog.w(TAG, "BDCloudMediaPlayer went away with unhandled events");
1776                return;
1777            }
1778
1779            switch (msg.what) {
1780                case MEDIA_PREPARED:
1781                    // selectResolutionByIndex may change option seek-at-start, reset here
1782                    player.setOption(OPT_CATEGORY_PLAYER, "seek-at-start", 0);
1783                    player.onPreparedStat();
1784                    player.notifyOnPrepared();
1785                    return;
1786
1787                case MEDIA_PLAYBACK_COMPLETE:
1788                    player.stayAwake(false);
1789                    player.onCompleteStat(msg.arg1, msg.arg2);
1790                    player.onEndStat();
1791                    player.notifyOnCompletion();
1792                    return;
1793
1794                case MEDIA_BUFFERING_UPDATE:
1795                    long bufferPosition = msg.arg1;
1796                    if (bufferPosition < 0) {
1797                        bufferPosition = 0;
1798                    }
1799
1800                    long percent = 0;
1801                    long duration = player.getDuration();
1802                    if (duration > 0 ) {
1803                        if((duration - bufferPosition) >= 1000) {
1804                            percent = bufferPosition * 100 / duration;
1805                        } else {
1806                            percent = (bufferPosition + 1000) * 100 /duration;
1807                        }
1808                    }
1809
1810                    if (percent >= 100) {
1811                        percent = 100;
1812                    }
1813
1814                    // DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d", percent,
1815                    // bufferPosition, duration);
1816                    player.notifyOnBufferingUpdate((int) percent);
1817                    return;
1818
1819                case MEDIA_SEEK_COMPLETE:
1820                    player.notifyOnSeekComplete();
1821                    return;
1822
1823                case MEDIA_SET_VIDEO_SIZE:
1824                    player.mVideoWidth = msg.arg1;
1825                    player.mVideoHeight = msg.arg2;
1826                    player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, player.mVideoSarNum,
1827                            player.mVideoSarDen);
1828                    return;
1829
1830                case MEDIA_ERROR:
1831                    DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
1832                    player.onErrorStat(msg.arg1, msg.arg2);
1833                    player.onEndStat();
1834                    if (!player.notifyOnError(msg.arg1, msg.arg2)) {
1835                        player.notifyOnCompletion();
1836                    }
1837                    player.stayAwake(false);
1838                    return;
1839                case MEDIA_PLAYBACK_STOPPED:
1840                    return;
1841                case MEDIA_INFO:
1842                    switch (msg.arg1) {
1843                        case MEDIA_INFO_VIDEO_RENDERING_START:
1844                            DebugLog.i(TAG, "Info: MEDIA_INFO_VIDEO_RENDERING_START\n");
1845                            break;
1846                        case MEDIA_INFO_MEDIA_CHANGE_START:
1847                            DebugLog.i(TAG, "Info: MEDIA_INFO_MEDIA_CHANGE_START\n");
1848                            break;
1849                        case MEDIA_INFO_MEDIA_CHANGE_END:
1850                            DebugLog.i(TAG, "Info: MEDIA_INFO_MEDIA_CHANGE_END : "+ msg.arg1 +"\n");
1851                            break;
1852                    }
1853                    player.onInfoStat(msg.arg1, msg.arg2);
1854                    player.notifyOnInfo(msg.arg1, msg.arg2);
1855                    // No real default action so far.
1856                    return;
1857                case MEDIA_TIMED_TEXT:
1858                    if (msg.obj == null) {
1859                        player.notifyOnTimedText(null);
1860                    } else {
1861                        BDTimedText text = new BDTimedText(new Rect(0, 0, 1, 1), (String) msg.obj);
1862                        player.notifyOnTimedText(text);
1863                    }
1864                    return;
1865                case MEDIA_NOP: // interface test message - ignore
1866                    break;
1867
1868                case MEDIA_SET_VIDEO_SAR:
1869                    player.mVideoSarNum = msg.arg1;
1870                    player.mVideoSarDen = msg.arg2;
1871                    player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, player.mVideoSarNum,
1872                            player.mVideoSarDen);
1873                    break;
1874
1875                case AVAPP_EVENT_METADATA:
1876                    Bundle meta = (Bundle) msg.obj;
1877                    player.notifyOnMetadata(meta);
1878                    for (String key : meta.keySet()) {
1879                        if(key.equals("playID") && !(meta.get(key).equals(""))) {
1880                            player.onMetadataStat(meta.getString(key));
1881                            playId = meta.getString(key);
1882                        }
1883                    }
1884                    break;
1885                case AVAPP_EVENT_DNS:
1886                    player.onDnsStat(msg.arg1);
1887                    break;
1888                case AVAPP_EVENT_HTTP_CONNECT_END:
1889                    player.onHttpStat(msg.arg1);
1890                    break;
1891
1892                default:
1893                    DebugLog.e(TAG, "Unknown message type " + msg.what);
1894            }
1895        }
1896    }
1897
1898    /*
1899     * Called from native code when an interesting event happens. This method just uses the EventHandler system to post
1900     * the event back to the main app thread. We use a weak reference to the original BDCloudMediaPlayer object so that
1901     * the native code is safe from the object disappearing from underneath it. (This is the cookie passed to
1902     * native_setup().)
1903     */
1904    @CalledByNative
1905    private static void postEventFromNative(Object weakThiz, int what, int arg1, int arg2, Object obj) {
1906        if (weakThiz == null) {
1907            return;
1908        }
1909
1910        @SuppressWarnings("rawtypes")
1911        BDCloudMediaPlayer mp = (BDCloudMediaPlayer) ((WeakReference) weakThiz).get();
1912        if (mp == null) {
1913            return;
1914        }
1915
1916        if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
1917            // this acquires the wakelock if needed, and sets the client side
1918            // state
1919            mp.start();
1920        }
1921        if (mp.mEventHandler != null) {
1922            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
1923            mp.mEventHandler.sendMessage(m);
1924        }
1925    }
1926
1927    /*
1928     * ControlMessage
1929     */
1930
1931    private OnControlMessageListener mOnControlMessageListener;
1932
1933    private void setOnControlMessageListener(OnControlMessageListener listener) {
1934        mOnControlMessageListener = listener;
1935    }
1936
1937    private interface OnControlMessageListener {
1938        String onControlResolveSegmentUrl(int segment);
1939    }
1940
1941    /*
1942     * NativeInvoke
1943     */
1944
1945    private OnNativeInvokeListener mOnNativeInvokeListener;
1946
1947    private void setOnNativeInvokeListener(OnNativeInvokeListener listener) {
1948        mOnNativeInvokeListener = listener;
1949    }
1950
1951    private interface OnNativeInvokeListener {
1952
1953        int CTRL_WILL_TCP_OPEN = 0x20001; // NO ARGS
1954        int CTRL_DID_TCP_OPEN = 0x20002; // ARG_ERROR, ARG_FAMILIY, ARG_IP,
1955        // ARG_PORT, ARG_FD
1956
1957        int CTRL_WILL_HTTP_OPEN = 0x20003; // ARG_URL, ARG_SEGMENT_INDEX,
1958        // ARG_RETRY_COUNTER
1959        int CTRL_WILL_LIVE_OPEN = 0x20005; // ARG_URL, ARG_RETRY_COUNTER
1960        int CTRL_WILL_CONCAT_RESOLVE_SEGMENT = 0x20007; // ARG_URL,
1961        // ARG_SEGMENT_INDEX,
1962        // ARG_RETRY_COUNTER
1963
1964        int EVENT_WILL_HTTP_OPEN = 0x1; // ARG_URL
1965        int EVENT_DID_HTTP_OPEN = 0x2; // ARG_URL, ARG_ERROR, ARG_HTTP_CODE
1966        int EVENT_WILL_HTTP_SEEK = 0x3; // ARG_URL, ARG_OFFSET
1967        int EVENT_DID_HTTP_SEEK = 0x4; // ARG_URL, ARG_OFFSET, ARG_ERROR,
1968        // ARG_HTTP_CODE
1969
1970        String ARG_URL = "url";
1971        String ARG_SEGMENT_INDEX = "segment_index";
1972        String ARG_RETRY_COUNTER = "retry_counter";
1973
1974        String ARG_ERROR = "error";
1975        String ARG_FAMILIY = "family";
1976        String ARG_IP = "ip";
1977        String ARG_PORT = "port";
1978        String ARG_FD = "fd";
1979
1980        String ARG_OFFSET = "offset";
1981        String ARG_HTTP_CODE = "http_code";
1982
1983        /*
1984         * @return true if invoke is handled
1985         * 
1986         * @throws Exception on any error
1987         */
1988        boolean onNativeInvoke(int what, Bundle args);
1989    }
1990
1991    @CalledByNative
1992    private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) {
1993        DebugLog.ifmt(TAG, "onNativeInvoke %x", what);
1994        if (weakThiz == null || !(weakThiz instanceof WeakReference<?>)) {
1995            throw new IllegalStateException("<null weakThiz>.onNativeInvoke()");
1996        }
1997
1998        @SuppressWarnings("unchecked")
1999        WeakReference<BDCloudMediaPlayer> weakPlayer = (WeakReference<BDCloudMediaPlayer>) weakThiz;
2000        BDCloudMediaPlayer player = weakPlayer.get();
2001        if (player == null) {
2002            throw new IllegalStateException("<null weakPlayer>.onNativeInvoke()");
2003        }
2004
2005        OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
2006        if (listener != null && listener.onNativeInvoke(what, args)) {
2007            return true;
2008        }
2009
2010        switch (what) {
2011            case OnNativeInvokeListener.CTRL_WILL_CONCAT_RESOLVE_SEGMENT: {
2012                OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener;
2013                if (onControlMessageListener == null) {
2014                    return false;
2015                }
2016
2017                int segmentIndex = args.getInt(OnNativeInvokeListener.ARG_SEGMENT_INDEX, -1);
2018                if (segmentIndex < 0) {
2019                    throw new InvalidParameterException("onNativeInvoke(invalid segment index)");
2020                }
2021
2022                String newUrl = onControlMessageListener.onControlResolveSegmentUrl(segmentIndex);
2023                if (newUrl == null) {
2024                    throw new RuntimeException(new IOException("onNativeInvoke() = <NULL newUrl>"));
2025                }
2026
2027                args.putString(OnNativeInvokeListener.ARG_URL, newUrl);
2028                return true;
2029            }
2030            default:
2031                return false;
2032        }
2033    }
2034
2035    /**
2036     * 自己指定codec,一般不需要设置
2037     */
2038    private interface OnMediaCodecSelectListener {
2039        String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level);
2040    }
2041
2042    private OnMediaCodecSelectListener mOnMediaCodecSelectListener;
2043
2044    /**
2045     * 设置codec解码器,一般不需要设置
2046     *
2047     * @param listener
2048     */
2049    private void setOnMediaCodecSelectListener(OnMediaCodecSelectListener listener) {
2050        mOnMediaCodecSelectListener = listener;
2051    }
2052
2053    public void resetListeners() {
2054        super.resetListeners();
2055        mOnMediaCodecSelectListener = null;
2056    }
2057
2058    private boolean isLocalFilePath(String strPath) {
2059
2060        if (null == strPath) {
2061            return false;
2062        }
2063
2064        if (strPath.startsWith("file://") || strPath.startsWith("/")) {
2065            return true;
2066        }
2067
2068        return false;
2069    }
2070
2071    @CalledByNative
2072    private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) {
2073        if (weakThiz == null || !(weakThiz instanceof WeakReference<?>)) {
2074            return null;
2075        }
2076
2077        @SuppressWarnings("unchecked")
2078        WeakReference<BDCloudMediaPlayer> weakPlayer = (WeakReference<BDCloudMediaPlayer>) weakThiz;
2079        BDCloudMediaPlayer player = weakPlayer.get();
2080        if (player == null) {
2081            return null;
2082        }
2083
2084        OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener;
2085        if (listener == null) {
2086            listener = DefaultMediaCodecSelector.sInstance;
2087        }
2088
2089        return listener.onMediaCodecSelect(player, mimeType, profile, level);
2090    }
2091
2092    private static class DefaultMediaCodecSelector implements OnMediaCodecSelectListener {
2093        public static final DefaultMediaCodecSelector sInstance = new DefaultMediaCodecSelector();
2094
2095        @SuppressWarnings("deprecation")
2096        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
2097        public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level) {
2098            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
2099                return null;
2100            }
2101
2102            if (TextUtils.isEmpty(mimeType)) {
2103                return null;
2104            }
2105
2106            DebugLog.i(TAG,
2107                    String.format(Locale.US, "onSelectCodec: mime=%s, profile=%d, level=%d", mimeType, profile, level));
2108            ArrayList<BDCloudMediaCodecInfo> candidateCodecList = new ArrayList<BDCloudMediaCodecInfo>();
2109            int numCodecs = MediaCodecList.getCodecCount();
2110            for (int i = 0; i < numCodecs; i++) {
2111                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
2112                DebugLog.d(TAG, String.format(Locale.US, "  found codec: %s", codecInfo.getName()));
2113                if (codecInfo.isEncoder()) {
2114                    continue;
2115                }
2116
2117                String[] types = codecInfo.getSupportedTypes();
2118                if (types == null) {
2119                    continue;
2120                }
2121
2122                for (String type : types) {
2123                    if (TextUtils.isEmpty(type)) {
2124                        continue;
2125                    }
2126
2127                    DebugLog.d(TAG, String.format(Locale.US, "    mime: %s", type));
2128                    if (!type.equalsIgnoreCase(mimeType)) {
2129                        continue;
2130                    }
2131
2132                    BDCloudMediaCodecInfo candidate = BDCloudMediaCodecInfo.setupCandidate(codecInfo, mimeType);
2133                    if (candidate == null) {
2134                        continue;
2135                    }
2136
2137                    candidateCodecList.add(candidate);
2138                    DebugLog.i(TAG, String.format(Locale.US, "candidate codec: %s rank=%d", codecInfo.getName(),
2139                            candidate.mRank));
2140                    candidate.dumpProfileLevels(mimeType);
2141                }
2142            }
2143
2144            if (candidateCodecList.isEmpty()) {
2145                return null;
2146            }
2147
2148            BDCloudMediaCodecInfo bestCodec = candidateCodecList.get(0);
2149
2150            for (BDCloudMediaCodecInfo codec : candidateCodecList) {
2151                if (codec.mRank > bestCodec.mRank) {
2152                    bestCodec = codec;
2153                }
2154            }
2155
2156            if (bestCodec.mRank < BDCloudMediaCodecInfo.RANK_LAST_CHANCE) {
2157                Log.w(TAG, String.format(Locale.US, "unaccetable codec: %s", bestCodec.mCodecInfo.getName()));
2158                return null;
2159            }
2160
2161            DebugLog.i(TAG, String.format(Locale.US, "selected codec: %s rank=%d", bestCodec.mCodecInfo.getName(),
2162                    bestCodec.mRank));
2163            return bestCodec.mCodecInfo.getName();
2164        }
2165    }
2166
2167    private static native void native_profileBegin(String libName);
2168
2169    private static native void native_profileEnd();
2170
2171    private static native void native_setLogLevel(int level);
2172}