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