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