├── .gitignore ├── LICENSE ├── README.md ├── README_en.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── a.mp4 │ ├── car.mp4 │ ├── city1.mp4 │ ├── dynamic_264_mid.mp4 │ ├── effect.mp4 │ ├── normal_264_mid.mp4 │ └── test_dynamic_264_mid.mp4 │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── yy │ │ └── yyeva │ │ └── player │ │ ├── EvaDemoActivity.kt │ │ ├── EvaDownloadDemoActivity.kt │ │ ├── EvaKeyDemoActivity.kt │ │ ├── EvaKeyListDemoActivity.kt │ │ ├── EvaKeyRecyclerAdapter.kt │ │ ├── FileUtil.kt │ │ ├── MainActivity.kt │ │ ├── bean │ │ └── VideoInfo.kt │ │ └── util │ │ ├── EvaCache.kt │ │ └── EvaDownloader.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ ├── bg.png │ ├── chang.png │ └── img.webp │ ├── drawable │ ├── ball_1.png │ ├── ball_2.png │ ├── ball_3.png │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_anim_demo_recycle.xml │ ├── activity_anim_simple_demo_p.xml │ ├── activity_main.xml │ └── item_animview.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.webp │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── resource └── out_3.gif ├── settings.gradle └── yyevac ├── .gitignore ├── CMakeLists.txt ├── build.gradle ├── proguard-rules.pro ├── publish.gradle └── src └── main ├── AndroidManifest.xml ├── cpp ├── bean │ ├── data.h │ ├── datas.h │ ├── descript.h │ ├── effect.cpp │ ├── effect.h │ ├── evaanimeconfig.cpp │ ├── evaanimeconfig.h │ ├── evaframe.cpp │ ├── evaframe.h │ ├── evaframeall.cpp │ ├── evaframeall.h │ ├── evaframeset.cpp │ ├── evaframeset.h │ ├── evasrc.cpp │ ├── evasrc.h │ ├── evasrcmap.cpp │ ├── evasrcmap.h │ └── pointrect.h ├── egl │ ├── eglcore.cpp │ ├── eglcore.h │ └── glbase.h ├── engine │ ├── bgrender.cpp │ ├── bgrender.h │ ├── evaengine.cpp │ ├── evaengine.h │ ├── irender.h │ ├── mp4render.cpp │ ├── mp4render.h │ ├── render.cpp │ ├── render.h │ ├── rendercontroller.cpp │ ├── rendercontroller.h │ ├── yuvrender.cpp │ └── yuvrender.h ├── mix │ ├── evamixrender.cpp │ ├── evamixrender.h │ ├── mixshader.cpp │ └── mixshader.h ├── util │ ├── glfloatarray.cpp │ ├── glfloatarray.h │ ├── parson.c │ ├── parson.h │ ├── shaderutil.cpp │ ├── shaderutil.h │ ├── stb_image.h │ ├── stb_image_write.h │ ├── texcoordsutil.cpp │ ├── texcoordsutil.h │ ├── textureloadutil.cpp │ ├── textureloadutil.h │ ├── vertexutil.cpp │ └── vertexutil.h ├── vulkan_wrapper │ ├── vulkan_wrapper.cpp │ └── vulkan_wrapper.h └── yyevajni.cpp ├── java └── com │ └── yy │ └── yyeva │ ├── EvaAnimConfig.kt │ ├── EvaAnimConfigManager.kt │ ├── EvaAnimPlayer.kt │ ├── IEvaRenderListener.kt │ ├── decoder │ ├── EvaDecoder.kt │ └── EvaHardDecoder.kt │ ├── file │ ├── EvaAssetsEvaFileContainer.kt │ ├── EvaFileContainer.kt │ ├── EvaPref.kt │ ├── EvaStreamContainerEva.kt │ ├── EvaStreamMediaDataSource.kt │ ├── FileUtil.kt │ └── IEvaFileContainer.kt │ ├── inter │ ├── IEvaAnimListener.kt │ ├── IEvaFetchResource.kt │ └── OnEvaResourceClickListener.kt │ ├── mix │ ├── EvaFrame.kt │ ├── EvaMixAnimPlugin.kt │ ├── EvaMixTouch.kt │ ├── EvaResource.kt │ └── EvaSrc.kt │ ├── plugin │ ├── EvaAnimPluginManager.kt │ └── IEvaAnimPlugin.kt │ ├── util │ ├── ELog.kt │ ├── EvaBitmapUtil.kt │ ├── EvaConstant.kt │ ├── EvaJniUtil.kt │ ├── EvaMediaUtil.kt │ ├── EvaVideoEntity.kt │ ├── PointRect.kt │ ├── ScaleTypeUtil.kt │ └── SpeedControlUtil.kt │ └── view │ ├── EvaAnimView.kt │ ├── EvaAnimViewV3.kt │ ├── EvaAudioPlayer.kt │ ├── IEvaAnimView.kt │ ├── InnerSurfaceView.kt │ └── InnerTextureView.kt └── res └── values └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | /.idea 11 | .DS_Store 12 | /build 13 | /*/jniLibs 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | local.properties 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YY-EVA Android 2 | 3 | 简体中文 | [English](./README_en.md) 4 | 5 | > 轻量级 高性能 跨平台 MP4 礼物播放器 6 | 7 | ## 支持本项目 8 | 9 | 请支持我们的项目,点击[⭐⭐⭐](https://github.com/yylive/YYEVA), 让更多的人看到该项目 10 | 11 | 12 | ## 案例演示 13 | 14 | 图片名称 15 | 16 | ## 介绍 17 | 18 | + YYEVAPlayer 是一个轻量的动画渲染库。通过[这里](https://github.com/yylive/YYEVA/blob/main/YYEVA%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83.md)导出动画文件 19 | + 通过[这里](https://github.com/yylive/YYEVA/tree/main/AEP/demo_aep)可以获取设计的测试资源文件 20 | + YYEVA-Android 使用Native Opengles 渲染视频,为你提供高性能、低开销的动画体验。 21 | 22 | ## 平台支持 23 | + 支持 [Android](https://github.com/yylive/YYEVA-Android)、[IOS](https://github.com/yylive/YYEVA-iOS)、[Web](https://github.com/yylive/YYEVA-Web) 点击了解详细接入 24 | + 资源制作的AE插件使用规范 [详情](https://github.com/yylive/YYEVA/tree/main/AEP) 25 | + 数据结构定义 [详情](https://github.com/yylive/YYEVA/blob/main/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md) 26 | + 项目相关文章、设计规范等 [详情](https://github.com/yylive/YYEVA) 27 | 28 | ## 用法 29 | 30 | 我们在这里介绍 YYEVA-Android 的用法。想要知道如何导出动画,点击[这里](https://github.com/yylive/YYEVA/blob/main/YYEVA%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83.md)。 31 | 32 | ### 使用Gradle安装依赖 33 | build.gradle 34 | ```groovy 35 | allprojects { 36 | repositories { 37 | maven { url 'https://jitpack.io' } 38 | } 39 | } 40 | ``` 41 | 42 | ```groovy 43 | dependencies { 44 | implementation 'com.github.yylive.YYEVA-Android:yyeva:1.0.17' 45 | } 46 | // 2.0.0-beta版本 47 | dependencies { 48 | implementation 'com.github.yylive.YYEVA-Android:yyeva:2.0.0-beta' 49 | } 50 | ``` 51 | 52 | ### 放置混合 mp4 文件 在Assets中 53 | 如需要使用SurfaceView可以使用EvaAnimView,需要使用TextureView可以使用EvaAnimViewV3,demo使用EvaAnimViewV3 54 | 55 | 替换元素接口 56 | ```kotlin 57 | interface IEvaFetchResource { 58 | // 获取图片 (暂时不支持Bitmap.Config.ALPHA_8 主要是因为一些机型opengl兼容问题) 59 | fun setImage(resource: EvaResource, result:(Bitmap?) -> Unit) 60 | 61 | // 获取文字 62 | fun setText(resource: EvaResource, result:(String?) -> Unit) 63 | 64 | // 资源释放通知 65 | fun releaseSrc(resources: List) 66 | 67 | //speed为倍速 68 | fun setVideoFps(fps: Int, speed: Float = 1.0f) 69 | //设置资源颜色区域和透明区域的对齐方式 70 | /* 71 | const val VIDEO_MODE_SPLIT_HORIZONTAL = 1 // 视频左右对齐(alpha左\rgb右) 72 | const val VIDEO_MODE_SPLIT_VERTICAL = 2 // 视频上下对齐(alpha上\rgb下) 73 | const val VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE = 3 // 视频左右对齐(rgb左\alpha右) 74 | const val VIDEO_MODE_SPLIT_VERTICAL_REVERSE = 4 // 视频上下对齐(rgb上\alpha下) 75 | const val YYEVAColorRegion_AlphaMP4_alphaHalfRightTop = 5 // 视频上下对齐(rgb上\alpha下)1/4 elpha 76 | */ 77 | fun setVideoMode(mode: Int) 78 | //设置起始播放位置 毫秒 79 | //硬解某些机型会有跳帧前几帧解析异常的问题,不建议使用。 80 | fun setStartPoint(startPoint: Long) 81 | //设置静音 82 | fun setMute(isMute: Boolean) 83 | //是否不透明度mp4 84 | fun setNormalMp4(isNormalMp4: Boolean) 85 | //是否停留在最后一帧 86 | fun setLastFrame(isSetLastFrame: Boolean) 87 | //设置日志打印 88 | fun setLog(log: IELog) 89 | } 90 | ``` 91 | 具体实现可以参照EvaDemoActivity的代码实验,替换自身mp4中的元素。 92 | 93 | 播放使用IEvaAnimView接口 94 | ```kotlin 95 | interface IEvaAnimView { 96 | ... 97 | //播放文件 98 | fun startPlay(file: File) 99 | //播放本地文件 100 | fun startPlay(assetManager: AssetManager, assetsPath: String) 101 | //停止播放 102 | fun stopPlay() 103 | //是否正在运行 104 | fun isRunning(): Boolean 105 | //循环播放 106 | fun setLoop(playLoop: Int) 107 | //设置背景图 108 | fun setBgImage(bg: Bitmap) 109 | ... 110 | } 111 | ``` 112 | 113 | 114 | 2.0.0-beta播放使用OptionParams类配置 115 | ```kotlin 116 | class OptionParams { 117 | var frameRate = 30 118 | var playCount = 1 119 | var isMute = false 120 | var isRemoteService = true //使用多进程 121 | var mp4Address = "" 122 | var scaleType = 1 // 1=>裁剪居中, 2=>全屏拉伸 3=>原资源大小 123 | var filterType = "" //高清算法 hermite lagrange 124 | } 125 | ``` 126 | 127 | ### 代码 128 | 129 | ## QQ交流群 130 | ![qqgroup](https://github.com/yylive/YYEVA/blob/main/img/qqgroup.png) 131 | 132 | ## 鸣谢 133 | + 感谢 [vap](https://github.com/Tencent/vap) 优秀的混合渲染方案、项目Render混合部分重用了 vap的方案 134 | 135 | ## Dev Team 136 | 137 | 138 | 139 | 144 | 145 | 146 |
140 | 141 |
142 | Cangwang 143 |
147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # YY-EVA Android 2 | > Lightweight,High Performance,Cross Platform,MP4 Gift Player 3 | 4 | ## Product show 5 | 6 | 图片名称 7 | 8 | ## Intruduction 9 | + YYEVAPlayer is a lightweight animation library with a simple yet powerful API。Reference [here](https://github.com/yylive/YYEVA/blob/main/YYEVA%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83.md) can easily export animation resources 10 | + YYEVA-iOS render with Metal library , providing you with a high-performance, low-cost animation experience. 11 | 12 | ## Platform support 13 | + Platform:[Android](https://github.com/yylive/YYEVA-Android), [iOS](https://github.com/yylive/YYEVA-iOS), [Web](https://github.com/yylive/YYEVA-Web) 14 | + Generation Tool : [AE plguin](https://github.com/yylive/YYEVA/tree/main/AEP) 15 | + [Data structure](https://github.com/yylive/YYEVA/blob/main/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md) 16 | + [Docs](https://github.com/yylive/YYEVA) 17 | 18 | ## Usage 19 | 20 | 我们在这里介绍 YYEVA-Android 的用法。想要知道如何导出动画,点击[这里](https://github.com/yylive/YYEVA/blob/main/YYEVA%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83.md)。 21 | 22 | ### Installation with Gradle 23 | build.gradle 24 | ```groovy 25 | allprojects { 26 | repositories { 27 | maven { url 'https://jitpack.io' } 28 | } 29 | } 30 | ``` 31 | 32 | ```groovy 33 | dependencies { 34 | implementation 'com.github.yylive.YYEVA-Android:yyeva:1.0.17' 35 | } 36 | // 2.0.0-beta 37 | dependencies { 38 | implementation 'com.github.yylive.YYEVA-Android:yyeva:2.0.0-beta' 39 | } 40 | ``` 41 | 42 | ### Animation with Key 43 | For SurfaceView can use EvaAnimView,For TextureView can useEvaAnimViewV3,demo show use EvaAnimViewV3 44 | 45 | change property interface 46 | ```kotlin 47 | interface IEvaFetchResource { 48 | // 获取图片 (暂时不支持Bitmap.Config.ALPHA_8 主要是因为一些机型opengl兼容问题) 49 | fun setImage(resource: EvaResource, result:(Bitmap?) -> Unit) 50 | 51 | // 获取文字 52 | fun setText(resource: EvaResource, result:(String?) -> Unit) 53 | 54 | // 资源释放通知 55 | fun releaseSrc(resources: List) 56 | } 57 | ``` 58 | You can find the example int the project in app module. 59 | 60 | Play with IEvaAnimView interface. 61 | ```kotlin 62 | interface IEvaAnimView { 63 | ... 64 | //播放文件 65 | fun startPlay(file: File) 66 | //播放本地文件 67 | fun startPlay(assetManager: AssetManager, assetsPath: String) 68 | //停止播放 69 | fun stopPlay() 70 | //是否正在运行 71 | fun isRunning(): Boolean 72 | //循环播放 73 | fun setLoop(playLoop: Int) 74 | //设置背景图 75 | fun setBgImage(bg: Bitmap) 76 | //speed为倍速 77 | fun setVideoFps(fps: Int, speed: Float = 1.0f) 78 | //设置资源颜色区域和透明区域的对齐方式 79 | /* 80 | const val VIDEO_MODE_SPLIT_HORIZONTAL = 1 // 视频左右对齐(alpha左\rgb右) 81 | const val VIDEO_MODE_SPLIT_VERTICAL = 2 // 视频上下对齐(alpha上\rgb下) 82 | const val VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE = 3 // 视频左右对齐(rgb左\alpha右) 83 | const val VIDEO_MODE_SPLIT_VERTICAL_REVERSE = 4 // 视频上下对齐(rgb上\alpha下) 84 | const val YYEVAColorRegion_AlphaMP4_alphaHalfRightTop = 5 // 视频上下对齐(rgb上\alpha下)1/4 elpha 85 | */ 86 | fun setVideoMode(mode: Int) 87 | //设置起始播放位置 毫秒 88 | //硬解某些机型会有跳帧前几帧解析异常的问题,不建议使用。 89 | fun setStartPoint(startPoint: Long) 90 | //设置静音 91 | fun setMute(isMute: Boolean) 92 | //是否不透明度mp4 93 | fun setNormalMp4(isNormalMp4: Boolean) 94 | //是否停留在最后一帧 95 | fun setLastFrame(isSetLastFrame: Boolean) 96 | //设置日志打印 97 | fun setLog(log: IELog) 98 | ... 99 | } 100 | ``` 101 | 2.0.0-beta use OptionParams to set 102 | ```kotlin 103 | class OptionParams { 104 | var frameRate = 30 105 | var playCount = 1 106 | var isMute = false 107 | var isRemoteService = true //使用多进程 108 | var mp4Address = "" 109 | var scaleType = 1 // 1=>裁剪居中, 2=>全屏拉伸 3=>原资源大小 110 | var filterType = "" //高清算法 hermite lagrange 111 | } 112 | ``` 113 | 114 | ## QQexchange group 115 | ![qqgroup](https://github.com/yylive/YYEVA/blob/main/img/qqgroup.png) 116 | 117 | ## 鸣谢 118 | + Thanks [vap](https://github.com/Tencent/vap) , our decoder module with good experiences of it. 119 | 120 | ## Dev Team 121 | 122 | 123 | 124 | 129 | 130 | 131 |
125 | 126 |
127 | Cangwang 128 |
132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | } 6 | 7 | android { 8 | compileSdk 31 9 | 10 | defaultConfig { 11 | applicationId "com.yy.yyeva" 12 | minSdk 21 13 | targetSdk 31 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | externalNativeBuild { 19 | cmake { 20 | cppFlags "-std=c++11 -fexceptions" 21 | // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' 22 | abiFilters = ["armeabi-v7a","arm64-v8a", 'x86', 'x86_64'] 23 | } 24 | } 25 | ndk { 26 | // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' 27 | setAbiFilters(['armeabi-v7a', 'arm64-v8a','x86', 'x86_64']) 28 | // abiFilters.clear() 29 | } 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | kotlinOptions { 43 | jvmTarget = '1.8' 44 | } 45 | lintOptions { 46 | abortOnError false 47 | } 48 | ndkVersion '21.0.6113669' 49 | 50 | // splits { 51 | // 52 | // // Configures multiple APKs based on ABI. 53 | // abi { 54 | // 55 | // // Enables building multiple APKs per ABI. 56 | // enable true 57 | // 58 | // // By default all ABIs are included, so use reset() and include to specify that we only 59 | // // want APKs for x86 and x86_64. 60 | // 61 | // // Resets the list of ABIs that Gradle should create APKs for to none. 62 | // reset() 63 | // 64 | // // Specifies a list of ABIs that Gradle should create APKs for. 65 | // include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' 66 | // 67 | // // Specifies that we do not want to also generate a universal APK that includes all ABIs. 68 | // universalApk false 69 | // } 70 | // } 71 | } 72 | 73 | dependencies { 74 | implementation fileTree(dir: 'libs', include: ['*.jar']) 75 | api project(':yyevac') 76 | // implementation 'com.github.yylive.YYEVA-Android:yyeva:1.1.11' 77 | implementation 'androidx.appcompat:appcompat:1.4.1' 78 | api 'com.google.android.material:material:1.5.0' 79 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 33 | 34 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/assets/a.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/a.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/car.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/car.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/city1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/city1.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/dynamic_264_mid.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/dynamic_264_mid.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/effect.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/effect.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/normal_264_mid.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/normal_264_mid.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/test_dynamic_264_mid.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/assets/test_dynamic_264_mid.mp4 -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/yy/yyeva/player/EvaKeyListDemoActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yy.yyeva.player 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.net.LinkAddress 6 | import android.os.Bundle 7 | import android.os.Environment 8 | import android.os.Handler 9 | import android.os.Looper 10 | import com.yy.yyeva.EvaAnimConfig 11 | import com.yy.yyeva.inter.IEvaAnimListener 12 | import com.yy.yyeva.util.ELog 13 | import com.yy.yyeva.util.IELog 14 | import android.util.Log 15 | import android.view.View 16 | import androidx.recyclerview.widget.LinearLayoutManager 17 | import androidx.recyclerview.widget.RecyclerView 18 | import com.yy.yyeva.player.bean.VideoInfo 19 | import kotlinx.android.synthetic.main.activity_anim_demo_recycle.* 20 | 21 | /** 22 | * 23 | * 必须使用YYEVA插件生成对应的mp4才能播放 24 | */ 25 | class EvaKeyListDemoActivity : Activity(), IEvaAnimListener { 26 | 27 | companion object { 28 | private const val TAG = "EvaDemoActivity" 29 | } 30 | 31 | private val dir by lazy { 32 | // 存放在sdcard应用缓存文件中 33 | getExternalFilesDir(null)?.absolutePath ?: Environment.getExternalStorageDirectory().path 34 | } 35 | 36 | // 视频信息 37 | private val videoInfo = VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be") 38 | 39 | private var adapter: EvaKeyRecyclerAdapter? = null 40 | private lateinit var layoutManager: LinearLayoutManager 41 | private var findLastVisibleItemPosition = 0 42 | private var findFirstVisibleItemPosition = 0 43 | 44 | private val uiHandler by lazy { 45 | Handler(Looper.getMainLooper()) 46 | } 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | setContentView(R.layout.activity_anim_demo_recycle) 51 | layoutManager = LinearLayoutManager(this) 52 | eva_recycler.layoutManager = layoutManager 53 | eva_recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { 54 | var dy = 0 55 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 56 | super.onScrolled(recyclerView, dx, dy) 57 | this.dy = dy 58 | } 59 | 60 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 61 | super.onScrollStateChanged(recyclerView, newState) 62 | if (newState == RecyclerView.SCROLL_STATE_IDLE) { 63 | findLastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() 64 | findFirstVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition() 65 | for (i in findFirstVisibleItemPosition..findLastVisibleItemPosition) { 66 | val viewHolder = eva_recycler.findViewHolderForAdapterPosition(i) as? EvaKeyRecyclerAdapter.EvaKeyHolder 67 | viewHolder?.play() 68 | } 69 | } 70 | } 71 | }) 72 | adapter = EvaKeyRecyclerAdapter() 73 | eva_recycler.adapter = adapter 74 | loadFile() 75 | } 76 | 77 | private fun init() { 78 | // 初始化日志 79 | initLog() 80 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 81 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 82 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 83 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 84 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 85 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 86 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 87 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 88 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 89 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 90 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 91 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 92 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 93 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 94 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 95 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 96 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 97 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 98 | adapter?.addBean(VideoInfo("effect.mp4", "400a778f258ed6bd02ec32defe8ca8be")) 99 | adapter?.notifyDataSetChanged() 100 | eva_recycler.post { 101 | val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() 102 | val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() 103 | ELog.i(TAG, "firstVisibleItemPosition $firstVisibleItemPosition, lastVisibleItemPosition $lastVisibleItemPosition") 104 | for (i in firstVisibleItemPosition..lastVisibleItemPosition) { 105 | val viewHolder = eva_recycler.findViewHolderForAdapterPosition(i) as? EvaKeyRecyclerAdapter.EvaKeyHolder 106 | viewHolder?.play() 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * 视频信息准备好后的回调,用于检查视频准备好后是否继续播放 113 | * @return true 继续播放 false 停止播放 114 | */ 115 | override fun onVideoConfigReady(config: EvaAnimConfig): Boolean { 116 | return true 117 | } 118 | 119 | /** 120 | * 视频开始回调 121 | */ 122 | override fun onVideoStart() { 123 | ELog.i(TAG, "onVideoStart") 124 | } 125 | 126 | override fun onVideoRestart() { 127 | ELog.i(TAG, "onVideoReStart") 128 | } 129 | 130 | /** 131 | * 视频渲染每一帧时的回调 132 | * @param frameIndex 帧索引 133 | */ 134 | override fun onVideoRender(frameIndex: Int, config: EvaAnimConfig?) { 135 | } 136 | 137 | /** 138 | * 视频播放结束(失败也会回调) 139 | */ 140 | override fun onVideoComplete(lastFrame: Boolean) { 141 | ELog.i(TAG, "onVideoComplete") 142 | } 143 | 144 | /** 145 | * 播放器被销毁情况下会调用 146 | */ 147 | override fun onVideoDestroy() { 148 | ELog.i(TAG, "onVideoDestroy") 149 | } 150 | 151 | /** 152 | * 失败回调 153 | * 一次播放时可能会调用多次 154 | * @param errorType 错误类型 155 | * @param errorMsg 错误消息 156 | */ 157 | override fun onFailed(errorType: Int, errorMsg: String?) { 158 | ELog.i(TAG, "onFailed errorType=$errorType errorMsg=$errorMsg") 159 | } 160 | 161 | private fun initLog() { 162 | ELog.isDebug = false 163 | ELog.log = object : IELog { 164 | override fun i(tag: String, msg: String) { 165 | Log.i(tag, msg) 166 | } 167 | 168 | override fun d(tag: String, msg: String) { 169 | Log.d(tag, msg) 170 | } 171 | 172 | override fun e(tag: String, msg: String) { 173 | Log.e(tag, msg) 174 | } 175 | 176 | override fun e(tag: String, msg: String, tr: Throwable) { 177 | Log.e(tag, msg, tr) 178 | } 179 | } 180 | } 181 | 182 | private fun loadFile() { 183 | val files = Array(1) { 184 | videoInfo.fileName 185 | } 186 | FileUtil.copyAssetsToStorage(this, dir, files) { 187 | uiHandler.post { 188 | init() 189 | } 190 | } 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /app/src/main/java/com/yy/yyeva/player/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package com.yy.yyeva.player 2 | 3 | import android.content.Context 4 | import com.yy.yyeva.util.ELog 5 | import java.io.* 6 | import java.security.MessageDigest 7 | 8 | object FileUtil { 9 | 10 | private val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') 11 | private val TAG = "FileUtil" 12 | 13 | fun copyAssetsToStorage(context: Context, dir: String, files: Array, loadSuccess:()->Unit) { 14 | Thread { 15 | var outputStream: OutputStream 16 | var inputStream: InputStream 17 | val buf = ByteArray(4096) 18 | val names = context.assets.list("") 19 | if (names == null) { 20 | ELog.e(TAG,"assets has no file") 21 | return@Thread 22 | } 23 | files.forEach { 24 | try { 25 | if (File("$dir/$it").exists()) { 26 | return@forEach 27 | } 28 | if (!names.contains(it)) { 29 | ELog.e(TAG,"assets has not $it") 30 | return@Thread 31 | } 32 | 33 | inputStream = context.assets.open(it) 34 | outputStream = FileOutputStream("$dir/$it") 35 | var length = inputStream.read(buf) 36 | while (length > 0) { 37 | outputStream.write(buf, 0, length) 38 | length = inputStream.read(buf) 39 | } 40 | outputStream.close() 41 | inputStream.close() 42 | } catch (e: IOException) { 43 | e.printStackTrace() 44 | return@Thread 45 | } 46 | } 47 | loadSuccess.invoke() 48 | }.start() 49 | 50 | } 51 | 52 | fun getFileMD5(file: File): String? { 53 | if (!file.exists() || !file.isFile || file.length() <= 0) { 54 | return null 55 | } 56 | var inputStream: InputStream? = null 57 | try { 58 | val md = MessageDigest.getInstance("MD5") 59 | inputStream = FileInputStream(file) 60 | val dataBytes = ByteArray(4096) 61 | var iRd: Int 62 | iRd = inputStream.read(dataBytes) 63 | while (iRd != -1) { 64 | md.update(dataBytes, 0, iRd) 65 | iRd = inputStream.read(dataBytes) 66 | } 67 | inputStream.close() 68 | val digest = md.digest() 69 | if (digest != null) { 70 | return bufferToHex(digest) 71 | } 72 | } catch (t: Throwable) { 73 | t.printStackTrace() 74 | } finally { 75 | if (inputStream != null) { 76 | try { 77 | inputStream.close() 78 | } catch (e: Throwable) { 79 | e.printStackTrace() 80 | } 81 | 82 | } 83 | } 84 | return null 85 | } 86 | 87 | private fun bufferToHex(bytes: ByteArray): String { 88 | return bufferToHex(bytes, 0, bytes.size) 89 | } 90 | 91 | private fun bufferToHex(bytes: ByteArray, m: Int, n: Int): String { 92 | val sb = StringBuffer(2 * n) 93 | val k = m + n 94 | for (l in m until k) { 95 | appendHexPair(bytes[l], sb) 96 | } 97 | return sb.toString() 98 | } 99 | 100 | private fun appendHexPair(bt: Byte, sb: StringBuffer) { 101 | val c0 = hexDigits[bt.toInt() and 0xf0 ushr 4] 102 | val c1 = hexDigits[bt.toInt() and 0x0f] 103 | sb.append(c0) 104 | sb.append(c1) 105 | } 106 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yy/yyeva/player/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yy.yyeva.player 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | btn1.setOnClickListener { 13 | startActivity(Intent(this, EvaDemoActivity::class.java)) 14 | } 15 | 16 | btn2.setOnClickListener { 17 | startActivity(Intent(this, EvaKeyDemoActivity::class.java)) 18 | } 19 | 20 | btn3.setOnClickListener { 21 | startActivity(Intent(this, EvaDownloadDemoActivity::class.java)) 22 | } 23 | btn4.setOnClickListener { 24 | startActivity(Intent(this, EvaKeyListDemoActivity::class.java)) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yy/yyeva/player/bean/VideoInfo.kt: -------------------------------------------------------------------------------- 1 | package com.yy.yyeva.player.bean 2 | 3 | data class VideoInfo(val fileName: String, val md5: String) -------------------------------------------------------------------------------- /app/src/main/java/com/yy/yyeva/player/util/EvaCache.kt: -------------------------------------------------------------------------------- 1 | package com.yy.yyeva.player.util 2 | 3 | import android.content.Context 4 | import android.os.Environment 5 | import com.yy.yyeva.util.ELog 6 | import java.io.File 7 | import java.net.URL 8 | import java.security.MessageDigest 9 | 10 | /** 11 | * EVA 缓存管理 12 | */ 13 | object EvaCache { 14 | enum class Type { 15 | DEFAULT, 16 | FILE 17 | } 18 | 19 | private const val TAG = "EvaCache" 20 | private var type: Type = Type.DEFAULT 21 | 22 | private var cacheDir: String = "/" 23 | get() { 24 | if (field != "/") { 25 | val dir = File(field) 26 | if (!dir.exists()) { 27 | dir.mkdirs() 28 | } 29 | } 30 | return field 31 | } 32 | 33 | 34 | fun onCreate(context: Context?) { 35 | onCreate(context, Type.DEFAULT) 36 | } 37 | 38 | fun onCreate(context: Context?, type: Type) { 39 | if (isInitialized()) return 40 | context ?: return 41 | // cacheDir = "${context.cacheDir.absolutePath}/eva/" 42 | cacheDir = context.getExternalFilesDir(null)?.absolutePath ?: Environment.getExternalStorageDirectory().path 43 | cacheDir = "$cacheDir/" 44 | File(cacheDir).takeIf { !it.exists() }?.mkdirs() 45 | EvaCache.type = type 46 | } 47 | 48 | /** 49 | * 清理缓存 50 | */ 51 | fun clearCache() { 52 | if (!isInitialized()) { 53 | ELog.e(TAG, "EVACache is not init!") 54 | return 55 | } 56 | EvaDownloader.threadPoolExecutor.execute { 57 | clearDir(cacheDir) 58 | ELog.i(TAG, "Clear eva cache done!") 59 | } 60 | } 61 | 62 | // 清除目录下的所有文件 63 | internal fun clearDir(path: String) { 64 | try { 65 | val dir = File(path) 66 | dir.takeIf { it.exists() }?.let { parentDir -> 67 | parentDir.listFiles()?.forEach { file -> 68 | if (!file.exists()) { 69 | return@forEach 70 | } 71 | if (file.isDirectory) { 72 | clearDir(file.absolutePath) 73 | } 74 | file.delete() 75 | } 76 | } 77 | } catch (e: Exception) { 78 | ELog.e(TAG, "Clear eva cache path: $path fail", e) 79 | } 80 | } 81 | 82 | fun isInitialized(): Boolean { 83 | return "/" != cacheDir && File(cacheDir).exists() 84 | } 85 | 86 | fun isDefaultCache(): Boolean = type == Type.DEFAULT 87 | 88 | fun isCached(cacheKey: String): Boolean { 89 | return if (isDefaultCache()) { 90 | buildCacheDir(cacheKey) 91 | } else { 92 | buildMp4File( 93 | cacheKey 94 | ) 95 | }.exists() 96 | } 97 | 98 | fun buildCacheKey(str: String): String { 99 | val messageDigest = MessageDigest.getInstance("MD5") 100 | messageDigest.update(str.toByteArray(charset("UTF-8"))) 101 | val digest = messageDigest.digest() 102 | var sb = "" 103 | for (b in digest) { 104 | sb += String.format("%02x", b) 105 | } 106 | return sb 107 | } 108 | 109 | fun buildCacheKey(url: URL): String = buildCacheKey(url.toString()) 110 | 111 | fun buildCacheDir(cacheKey: String): File { 112 | return File("$cacheDir$cacheKey/") 113 | } 114 | 115 | fun buildMp4File(cacheKey: String): File { 116 | return File("$cacheDir$cacheKey.mp4") 117 | } 118 | 119 | fun buildAudioFile(audio: String): File { 120 | return File("$cacheDir$audio.mp3") 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable-xhdpi/bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/chang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable-xhdpi/chang.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable-xhdpi/img.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/ball_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable/ball_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ball_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable/ball_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ball_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yylive/YYEVA-Android/1ae13e67953fa597200d662fd476db4f9ed1bcf0/app/src/main/res/drawable/ball_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_anim_demo_recycle.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_anim_simple_demo_p.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 15 | 16 | 23 |