KMedia

Project Url: jcodeing/KMedia
Introduction: 为 Android 打造的应用级媒体框架, 助你快速搭建媒体应用.用最简单最灵活的 API 来实现媒体应用中繁琐的需求.欢迎使用 KMedia.
More: Author   ReportBugs   DemoAPK   
Tags:
kmedia-framework-android-java-media-video-audio-player-

KMedia-Logo

一个为 Android 打造的应用级媒体框架, 它可以助你快速搭建媒体应用. 内部重新定义 Android MediaPlayer API 并对其封装, 简化和扩展一些原生 API 不支持的功能. 其中涵盖了, AB 播放/循环 位置单元/间隔/循环 变速播放 媒体队列管理 媒体服务/绑定 音频后台/通知栏控制 媒体按键自定义处理 音频焦点管理 媒体引擎切换/扩展... 等功能的快速实现. 以及, 对视频播放实现方面的封装. 其中将视频视图主要分为: 绘制层 控制组 控制层, 三个部分. 从而能够快速并灵活的实现 Video 相关应用的大部分功能, 包括 视频浮窗/拖动/调整位置大小 横竖屏自动切换 全屏锁定 手势调整亮度/音量/进度 字幕/切换/拖动 视频段落/间隔复读 视频续集/列表/循环播放 动态切换视频控制层 控制层分离... 等功能的快速实现.

KMedia 框架可以直接从 JCenter 添加依赖, 或者以子模块的形式添加到工程后再依赖.

从 JCenter 添加依赖 快捷方便

KMedia-Core KMedia-Uie KMedia-Exo

compile 'com.jcodeing:kmedia-core:r1.0.10' //KMedia 核心模块
compile 'com.jcodeing:kmedia-uie:r1.0.10' //KMedia 界面扩展模块 (可选)
compile 'com.jcodeing:kmedia-exo:r1.0.10' //KMedia 媒体引擎扩展模块 (可选)

添加 Submodule 到工程后再依赖 自定义强

KMedia-Core-Fork KMedia-Uie-Fork KMedia-Exo-Fork

Step 1: Fork(↑)模块仓库到你的 Github.

当然, 你也可以 Fork 我的仓库到你的私人 remote 仓库.
这步主要是为了生成一个 remote 仓库地址.

Step 2: 在你工程的根目录, 用 git submodule 命令添加子模块.

git submodule add "Your KMedia-Core Remote Repositorie Path" kmedia-core
git submodule add "Your KMedia-Uie Remote Repositorie Path" kmedia-uie
git submodule add "Your KMedia-Mpe Remote Repositorie Path" kmedia-mpe

Step 3: 根据上面所添加的子模块配置你工程的 settings.gradle 文件.

include ':kmedia-core'
include ':kmedia-uie'
include ':kmedia-exo'
project(':kmedia-exo').projectDir = new File(settingsDir, 'kmedia-mpe/exo')

Step 4: 经过上面的添加配置步骤后, 你就可以在本地依赖并随时开发自定义 KMedia 的各个模块.

compile project(':kmedia-core')
compile project(':kmedia-uie')
compile project(':kmedia-exo')

演示

Demo-Screen-Record-1Demo-UiDemo-Screen-Record-2
===============================点击下载 KMedia Demo 4.9 MB===============================

k, 2017-9-24, PrivateKeyEntry,
证书指纹 (SHA1): 43:79:8C:03:1A:8A:2A:E8:CC:CA:D4:E3:63:9A:0F:70:29:C4:69:9B

Example 1: 简单的视频播放

首先在 Layout 中添加 PlayerView.

<com.jcodeing.kmedia.video.PlayerView
   android:id="@id/k_player_view"
   android:layout_width="match_parent"
   android:layout_height="200dp"/>

然后在 Activity 中将 Player 设置给 PlayerView 即可去播放.

Player player = new Player(context).init(new AndroidMediaPlayer());
((PlayerView) findViewById(R.id.k_player_view)).setPlayer(player);
player.play(Uri.parse("video"));

最后当你确定不再播放时, 请调用以下方法去释放资源对象.

playerView.finish();
player.shutdown();

Example 2: 简单的视频浮窗

把 Player 交给 VideoFloatingWindowController 去显示即可浮屏播放.

Player player = new Player(context).init(new ExoMediaPlayer(context));
new VideoFloatingWindowController(getApplicationContext()).show(player);
layer.play(Uri.parse("video"));

最后当你确定不再播放时, 请调用以下方法去释放资源对象.

player.shutdown();
vFloatingWinControler.hide();

温馨提示,请根据你的具体使用需求合理申请 WINDOW 权限. 点击查看源码片段

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || OS.i().isMIUI()) {
  //<!--Using WindowManager.LayoutParams.TYPE_PHONE For Floating Window View-->
  //<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;//2002
} else {
  layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;//2005
}

Example 3: 简单的视频全屏

首先在 AndroidManifest 文件中, 添加 configChanges 到你的 Activity.

<activity
  android:configChanges="orientation|keyboardHidden|screenLayout|screenSize"
</activity>

然后在 Activity 中覆写 onConfigurationChanged, 并在方法内处理横竖屏切换时需要隐藏和显示的 View.

@Override
public void onConfigurationChanged(Configuration newConfig) {
  super.onConfigurationChanged(newConfig);
  //Dispose view in control layer switch
  if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
    other.setVisibility(View.GONE);
  } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
    other.setVisibility(View.VISIBLE);
  }
}

最后调用 playerView 启用方向助手即可实现简单的横竖屏自动切换.

playerView.setOrientationHelper(this, 1);//enable sensor

Example 4: 简单的使用控制层

ControlLayerView 在 Layout 中的简单使用.

<com.jcodeing.kmedia.video.PlayerView
  android:id="@id/k_player_view"
  android:layout_width="match_parent"
  android:layout_height="200dp">
  <!--app:use_gesture_detector="true"-->
  <!--可以开启简单的手势,上下滑动调整音量,左右滑动调整进度,双击播放/暂停等-->
  <com.jcodeing.kmedia.video.ControlLayerView
    android:id="@id/k_ctrl_layer_port"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:use_part_top="true">
    <!--使用 KID @id/k_ctrl_layer_port 可以标识为竖屏下的默认控制层-->
    <!--使用 KID @id/k_ctrl_layer_land 可以标识为横屏下的默认控制层-->

    <!--app:interaction_area_always_visible="true"-->
    <!--可以使各个 Part 部分一直为显示状态,不会在超时等情况下自动隐藏 -->

    <!--app:part_top_min_height="17dp"-->
    <!--该属性可以直接设置顶部 Part 的最小高度,另外相对应的还有 _bottom -->

    <!--View frame explain:
      |************************|
      |          top           |
      |                        |
      |                        |
      | left    middle   right |
      |                        |
      |                        |
      |         bottom         |
      |************************|-->
     <!--其中 bottom,middle 都为默认使用-->
     <!--想用其他 Part 请设置 app:use_part_xxx="true"-->

     <!--使用 KID 标识你要添加的各个 Part-->
     <!--=========@Top@=========-->
     <!--android:id="@id/k_ctrl_layer_part_top"-->
     <!--=========@Bottom@=========-->
     <!--android:id="@id/k_ctrl_layer_part_bottom"-->
     <!--=========@Xxx@=========-->
     <!--android:id="@id/k_ctrl_layer_part_Xxx"-->
     <XxxxLayout
       android:id="@id/k_ctrl_layer_part_Xxx"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <!--XxxxLayout 你可以随意替换为 FrameLayout,RelativeLayout 等-->
       <!--内部你可以随心所欲添加你要的 View-->
       <!--其中可以使用一些公共的 Id 来快速完成不同的需求-->

       <!--使用 KID @id/k_play 和 @id/k_pause-->
       <!--内部会自动帮你完成视频播放过程中,播放/暂停控制按钮的各个响应-->

       <!--使用 KID @id/k_position_tv 和 @id/k_duration_tv-->
       <!--内部会自动按照你的标识,去显示播放进度和总时长-->

       <!--使用 KID @id/k_progress_any 和 @id/k_progress_bar-->
       <!--内部会帮你处理,播放进展 两个 id 可以一起使用-->
       <!--其中 k_progress_any 可以为你自定的任何 View 只要实现 ProgressAny 这个接口-->

       <!--Tips: 这些 KID 不局限于 Part 内部使用,只要在控制层内都可以-->
     </XxxxLayout>
     <!--注意: 上面提到的 Top,Bottom,Xxx 这些方位 Part 的 layout_xx 属性
      都是相对于内部方位容器的, 它们在控制层内的位置是固定的-->
     <!--注意: 下面这两个 Part 的 layout_xx 属性是相对于这个控制层的-->
     <!--=========@Buffer@=========-->
     <!--android:id="@id/k_ctrl_layer_part_buffer"-->
     <!--用于快速标识该 View 为播放中视频缓冲时显示-->
     <!--=========@Tips@=========-->
     <!--android:id="@id/k_ctrl_layer_part_tips_tv"-->
     <!--这个 TextView 用于内部小提示,比如手势改变音量等-->
  </com.jcodeing.kmedia.video.ControlLayerView>
</com.jcodeing.kmedia.video.PlayerView>

ControlLayerView 在 Activity 中的简单使用. 点击查看源码片段

// ============================@ControlLaye@============================
// 根据自己添加的控制层 id,find 到控制层 View. 下面简单用控制层 KID @id/k_ctrl_layer_port 来演示
ControlLayerView portCtrlLayer = (ControlLayerView) findViewById(R.id.k_ctrl_layer_port);
// 在 initPart 时,注意确保这个 Part 在 Layout 中 已经 app:use_part_xxx="true" (默认 Part 就不用再设置了)

// Use top title
portTitle = (TextView) portCtrlLayer.initPart(R.id.part_top_tv);
// 自己处理 Title, 如果使用了 MediaQueue 则可以在 onCurrentQueueIndexUpdated(.)回调用更新你的 Title

//Custom top left iv
portCtrlLayer.initPartIb(R.id.part_top_left_ib,
    R.drawable.ic_go_back, "goBack").setOnClickListener(this);
//Custom bottom right iv
portCtrlLayer.initPartIb(R.id.part_bottom_right_ib,
    R.drawable.ic_go_full_screen, "goFullScreen").setOnClickListener(this);
// 自己处理不同 Part 按钮的点击事件
// 其中 goFullScreen Api 调用: playerView.getOrientationHelper().goLandscape();

//Custom remove position
portCtrlLayer.removePart(R.id.k_position_tv);
//Custom change bottom background
portCtrlLayer.findPart(R.id.k_ctrl_layer_part_bottom)
    .setBackgroundResource(android.R.color.transparent);
//Custom change middle background
portCtrlLayer.findPart(R.id.k_ctrl_layer_part_middle)
    .setBackgroundResource(android.R.color.transparent);
//Custom change middle play/pause/buffer/... size
((ImageButton) portCtrlLayer.findPart(R.id.k_play,
    Metrics.dp2px(this, 31f), Metrics.dp2px(this, 31f))).setScaleType(ScaleType.FIT_CENTER);
//最后如果上面的改动中,涉及到 SmartView(k_play/pause..) 需要去更新下
portCtrlLayer.updateSmartView();

Example 5: 简单的使用 PlayerService/Binding

首先在 AndroidManifest 文件中, 添加 service.

<service android:name="com.jcodeing.kmedia.service.PlayerService"/>

然后在 Activity 中进行 PlayerBinding. 点击查看源码片段

player = new PlayerBinding(this, PlayerService.class, new BindPlayer() {
  @Override
  public IPlayer onBindPlayer() {
    return new Player(getApplicationContext())
        .init(new ExoMediaPlayer(getApplicationContext()));
  }//Player service bind player, call back.(if is bound, not call back)
}, new BindingListener() {
  @Override
  public void onFirstBinding(PlayerService service) {
    //do something player service init operation.
    //可以在这里给服务设置一个 Notifier,来实现在通知栏中控制音频播放
    service.setNotifier(new AudioQueueNotifier());
  }//First binding call back.(if first binding finish, not call back)

  @Override
  public void onBindingFinish() {
    player.xxx();
  }//Binding finish. Can play.
});
}

Example X: 更多例子参考

MainActivity

AudioQueueActivity

VideoQueueActivity

VideoMultipleActivity

文档

API - IPlayer

Video

void setVideo(SurfaceView surfaceView);
void setVideo(TextureView textureView);
void clearVideo();

AB

P setAB(long startPos, long endPos);
P setABLoop(int loopMode, int loopInterval);
P setAB(long startPos, long endPos, int loopMode, int loopInterval);
P setClearAB(boolean autoClear);
///////////////////////////////////////////////////////////
player.setAB(abStart, abEnd, abLoop, abLoopInterval).play();

PlaybackSpeed

boolean setPlaybackSpeed(float speed);
float getPlaybackSpeed();

SeekTo

boolean seekTo(long ms);
boolean seekTo(long ms, int processingLevel);
void seekToPending(long ms);
long seekToProgress(int progress, int progressMax);
boolean fastForwardRewind(long ms);

PositionUnit

P setPositionUnitList(IPositionUnitList posUnitList);
int getCurrentPositionUnitIndex();
void setCurrentPositionUnitIndex(int posUnitIndex);
long seekToPositionUnitIndex(int posUnitIndex);
int calibrateCurrentPositionUnitIndex(long position);
P setEnabledPositionUnitLoop(boolean enabled, int loopMode, int loopInterval);
P setPositionUnitLoopIndexList(ArrayList<Integer> posUnitLoopIndexList);

API - IMediaPlayer

如果你想扩展媒体引擎, 可以去实现 IMediaPlayer 接口.

Source

void setDataSource(String path);
void setDataSource(Context context, Uri uri);
void setDataSource(Context context, Uri uri, Map<String, String> headers);
void setDataSource(FileDescriptor fd);
Uri getDataSource();
void prepareAsync();

Control

boolean start();
boolean pause();
boolean seekTo(long ms);
void stop();

Control

void setAudioStreamType(int streamtype);
void setVolume(float leftVolume, float rightVolume);
void setDisplay(SurfaceHolder sh);
void setSurface(Surface surface);
void setScreenOnWhilePlaying(boolean screenOn);
void setLooping(boolean looping);
boolean isLooping();
int getVideoWidth();
int getVideoHeight();
int getAudioSessionId();

Public - KID

<!--========================================================-->
<!--=========@Use prefix "k_" to avoid duplication@=========-->
<!--========================================================-->

<!--=========@Player@=========-->
<item name="k_player_view" type="id"/>
<item name="k_content_frame" type="id"/>
<item name="k_shutter" type="id"/>

<!--=========@Control@=========-->
<item name="k_ctrl_group" type="id"/>
<item name="k_ctrl_layer_port" type="id"/>
<item name="k_ctrl_layer_land" type="id"/>
<!--=========@Layer Part-->
<item name="k_ctrl_layer_part_top" type="id"/>
<item name="k_ctrl_layer_part_bottom" type="id"/>
<item name="k_ctrl_layer_part_left" type="id"/>
<item name="k_ctrl_layer_part_right" type="id"/>
<item name="k_ctrl_layer_part_middle" type="id"/>
<item name="k_ctrl_layer_part_buffer" type="id"/>
<item name="k_ctrl_layer_part_tips_tv" type="id"/>
<!--=========@Smart View-->
<item name="k_play" type="id"/>
<item name="k_pause" type="id"/>
<item name="k_prev" type="id"/>
<item name="k_next" type="id"/>
<item name="k_rew" type="id"/>
<item name="k_ffwd" type="id"/>
<item name="k_position_tv" type="id"/>
<item name="k_duration_tv" type="id"/>
<item name="k_progress_bar" type="id"/>
<item name="k_progress_any" type="id"/>
<!--=========@Extend-->
<item name="k_switch_control_layer" type="id"/>

<!--=========@Floating@=========-->
<item name="k_floating_view_close" type="id"/>
<item name="k_floating_view_drag_location" type="id"/>
<item name="k_floating_view_drag_size" type="id"/>

<!--=========@....................................@=========-->

Public - Attrs

<!--=========@AControlGroupView@=========-->
<attr format="boolean" name="use_gesture_detector"/>
<declare-styleable name="AControlGroupView">
  <attr format="integer" name="show_timeout"/>
  <attr format="integer" name="rewind_increment"/>
  <attr format="integer" name="fast_forward_increment"/>
  <attr format="reference" name="default_control_layer_id"/>
  <attr name="use_gesture_detector"/>
</declare-styleable>

<!--=========@AControlLayerView@=========-->
<declare-styleable name="AControlLayerView">
  <attr format="reference" name="control_layer_layout_id"/>
  <!--=========@Part-->
  <!--=====@Interaction Area-->
  <attr format="boolean" name="interaction_area_always_visible"/>
  <attr format="boolean" name="use_part_top"/>
  <attr format="boolean" name="use_part_bottom"/>
  <attr format="boolean" name="use_part_left"/>
  <attr format="boolean" name="use_part_right"/>
  <attr format="boolean" name="use_part_middle"/>
  <attr format="boolean" name="use_part_view_animation"/>
  <attr format="dimension" name="part_top_min_height"/>
  <attr format="dimension" name="part_bottom_min_height"/>
  <!--=====@Other-->
  <attr format="boolean" name="use_part_buffer"/>
  <attr format="boolean" name="use_part_tips"/>
</declare-styleable>

<!--=========@AspectRatioView@=========-->
<!--Must be kept in sync with AspectRatioView-->
<attr format="enum" name="resize_mode">
  <enum name="fit" value="0"/>
  <enum name="fixed_width" value="1"/>
  <enum name="fixed_height" value="2"/>
  <enum name="fill" value="3"/>
</attr>
<declare-styleable name="AspectRatioView">
  <attr name="resize_mode"/>
</declare-styleable>

<!--=========@APlayerView@=========-->
<!--Must be kept in sync with APlayerView-->
<attr format="enum" name="surface_type">
  <enum name="none" value="0"/>
  <enum name="surface_view" value="1"/>
  <enum name="texture_view" value="2"/>
</attr>
<declare-styleable name="APlayerView">
  <attr name="surface_type"/>
  <attr format="reference" name="player_layout_id"/>
  <attr format="boolean" name="use_control_group"/>
  <!--Other-->
  <attr name="resize_mode"/>
  <attr name="android:layout_height"/>
  <attr name="use_gesture_detector"/>
</declare-styleable>

<!--=========@.................@=========-->

注意

Vector 低版本兼容

KMedia 各个 Module 中均使用支持库, 来实现 Android2.1(API 7)及更高版本中支持 VectorDrawable

vectorDrawables.useSupportLibrary = true

值得注意的是, 如果你的应用需要运行在低于 Android 5.0(API 21)的设备上,
并使用 KMedia 中 ANotifier 的 createSimpleMediaNotificationBuilder 时,
你就要考虑去兼容下低版本中 Vector 用在 Notification 上的情况.
可以将 KMedia 中用于 Notification 的 Vector 转成 png, 放到Res 中. 更多参见.

RequiresPermission 权限申请

KMedia 各个 Module 中不会去主动申请任何相关权限,
对于部分需要权限的 API, 会加上注解 RequiresPermission 来提醒开发者去申请权限.
值得注意的是, 在显示浮窗相关的 API 处, 如果需要做兼容处理时, 需要使用到 WINDOW 权限.
此处并未加权限注解, 但在源码中有详细的注释, 上文中也提到了这点.
因为不是所有用户都需要去做兼容浮窗. 这个就要根据你的具体使用需求合理申请 WINDOW 权限.
还有一些特殊的定制系统, 需要手动处理 显示悬浮窗.

开发

很高兴同大家一起来开发 KMedia. 做开源贡献者的一夸克.

首先我们做下开发准备工作

Step 1: 克隆 KMedia 仓库的开发分支到本地

git clone -b develop https://github.com/jcodeing/KMedia.git

Step 2: 初始化 KMedia 各个模块

也可以使用 git submodule 命令(init & update)来完成

./init_modules.sh

Step 3: 添加属于你的远程仓库地址

远程仓库地址, 可以通过 Fork 我的各个模块得到.

git remote add fork "Your KMedia Remote Repositorie Path"
cd core
git remote add fork "Your KMedia-Core Remote Repositorie Path"
cd uie
git remote add fork "Your KMedia-Uie Remote Repositorie Path"
cd mpe
git remote add fork "Your KMedia-Mpe Remote Repositorie Path"

完成上面的步骤后用 IDE(AndroidStudio)打开 KMedia

其次我们来了解下开发规范

Code Style

KMedia 代码统一使用 Google Style 来 Reformat Code

由于 KMedia 为开源项目, 开发过程中可能会涉及到多种 Reformat Code 风格,
为了便于大家阅读和编码, 所以在此统一使用 Google Style 来 Reformat Code.

代码中使用以下注释条来划分具体的功能模块

你可以将下面的注释条, 加入到 IDE 的 Live Templates 中, 从而方便使用.

// ============================@Xxx Xxx@============================
// ============================@Xxx
// =========@Xxx Xxx@=========
// =========@Xxx

最后我们具体谈下从无到有的编码贡献流程.

首先, 你要有一个需求

  • 优化 KMedia.
  • 解决 KMedia 的 Bug,修复它.
  • 一拍脑门得到一个 Idea, 想加到 KMedia 中, 让大家一起用.
  • 等等...

然后, 你就可以狂甩代码了

甩码过程中, 请遵循 KMedia 的编码规范.

其次, 注意下, 在编码完成部分后先提交到自己(Fork)的远程仓库

在准备工作中, 我们各自都有一个属于自己的远程仓库.
当代码还是雏形时, 先提交到自己的仓库进行维护. 同时, 可以随时合并 Origin 仓库的最新代码.

最后, 当你完成这个需求或者确定要先提交到 Origin 仓库时

可以到你 Fork 的 Github 仓库主页, 点击 Pull request 去 Create pull request 给我.
我看到后, 会第一时间处理你的 request. 从而时 KMedia 更加完善和强固.

===========================感谢你使用 KMedia===========================

Android 开发经验分享
随手点击下面广告支持本站吧
 
Android 开发经验分享