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}