[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\ntmp.txt"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "![logo](./images/default_art.png)\n# ExoVideoView\nExoVideoView is based on [ExoPlayer](https://github.com/google/ExoPlayer).\n\n[中文移步至此](./README_CN.md).\n\n![demo](./images/demo.gif)\n\n### Planning new versions\n\n**What's in ExoVideoView**\n\n    1.Process AudioFocus automatically.\n    2.Process its orientation by sensor automatically\n    3.simple gesture action supported.\n    4.multiple video quality supported\n    5.you can add custom views to the default controller.\n    6.multiple resize-mode supported\n    7.custom controller supported.\n    8.change the widget's visibility if you like.\n## Using ExoVideoView\n### 1.Dependency\nThe easiest way to get started using ExoVideoView is to add it as a gradle dependency. You need to make sure you have the jitpack repositories included in the build.gradle file in the root of your project:\n```\nallprojects {\n\t\trepositories {\n\t\t\t...\n\t\t\tmaven { url 'https://jitpack.io' }\n\t\t}\n\t}\n```\nNext add a gradle compile dependency to the build.gradle file of your app module:\n```\nimplementation 'com.github.JarvanMo:ExoVideoView:2.1.6'\n```\n### 2.In Layout\nDeclare ExoVideoView in your layout file as :\n```xml\n<com.jarvanmo.exoplayerview.ui.ExoVideoView\n     android:id=\"@+id/videoView\"\n     android:layout_width=\"match_parent\"\n     android:layout_height=\"300dp\"/>\n```\n### 3.In Java\nExoVideoView provides built-in ```Player``` for convenience,so we can play a video as\n```java\nSimpleMediaSource mediaSource = new SimpleMediaSource(url);//uri also supported\nvideoView.play(mediaSource);\nvideoView.play(mediaSource,where);//play from a particular position\n```\nPassing a player outside to ExoVideoView:\n```java\nvideoView.setPlayer(player);\n```\nNote:never forget to release ExoPlayer:\n```java\nvideoView.releasePlayer();\n```\nsee details in [demo]().\n\n### 3.Orientation Management\nThe ExoVideoView can handle its orientation by sensor automatically only when ExoVideoVIew has a not-null OrientationListener  :\n```java\n    videoView.setOrientationListener(orientation -> {\n            if (orientation == SENSOR_PORTRAIT) {\n                //do something\n            } else if (orientation == SENSOR_LANDSCAPE) {\n                //do something\n            }\n        });\n```\n> NOTE:When the ExoVideoView handle its orientation automatically,The ExoVideoView will call ```activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)``` or ```activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);``` if the ```context``` in controller is an Activity.\nThe fullscreen management is the same as orientation management.\n\n### 4.Back Events\nFirst,override onKeyDown:\n```java\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            return videoView.onKeyDown(keyCode, event);\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n```\nThen passing a backListener to ExoVideoView:\n```java\n    videoView.setBackListener((view, isPortrait) -> {\n            if (isPortrait) {\n              //do something\n            }\n            return false;\n        });\n```\nIf return value is ```true```, operation will be interrupted.Otherwise,ExoVideoView handle its orientation by itself and ```OrientationLister.onOrientationChange()``` will be caled.\n## Advance\n### 1.Multi-Quality\nExoVideoView also provides a built-in multi-quality selector.The multi-quality selector\nwill be added to ```overlayFrameLayout``` if  multi-quality is enabled and  ```ExoMediaSource``` are given different qualities in current version.\n```java\n        List<ExoMediaSource.Quality> qualities = new ArrayList<>();\n        ExoMediaSource.Quality quality =new SimpleQuality(quality,mediaSource.url());\n        qualities.add(quality);\n        mediaSource.setQualities(qualities);\n```\n\n### 2.Controller Display Mode\n```ExoVideoPlaybackController``` are divided into four parts:\n```\n1.Top\n2.Top Landscape\n3.Bottom\n4.Bottom Landscape\n```\nEach of them can be hidden or shown:\n```xml\n app:controller_display_mode=\"all|none|top|top_landscape|bottom|bottom_landscape\"\n```\nin java:\n```java\n  videoView.setControllerDisplayMode(mode);\n```\n### 3.Add Custom View To Controller\nViews can be added to ```ExoVideoPlaybackController``` in java.\n```java\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP, view);\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP_LANDSCAPE, view);\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_BOTTOM_LANDSCAPE, view);\n```\n### 4.Specifying A custom Layout File\nDefining your own ```exo_video_playback_control_view.xml``` is useful to customize the layout of ```ExoVideoPlaybackControlView``` throughout your application. It's also possible to customize the layout for asingle instance in a layout file. This is achieved by setting the  controller_layout_id attribute on a ```ExoVideoPlaybackControlView```. This will cause the specified layout to be inflated instead of ```code exo_video_playback_control_view.xml``` for only the instance on which the attribute is set.\n```xml\napp:controller_layout_id=\"@layout/my_controller\"\n```\n### 5.Change Visibility\nSometimes,we may not like the back buttons.So let's hide it:\n```java\nvideoView.changeWidgetVisibility(R.id.exo_player_controller_back,View.INVISIBLE);\n```\nFor more widgets you can hide or show,see [IDS_IN_CONTROLLER](./exoplayerview/src/main/res/values/ids.xml).\n> NOTE:This is a dangerous operation because I don't know what  will happen to the UI.\n## Others\n\n```\n  app:controller_background=\"@android:color/holo_orange_dark\"\n  app:use_artwork=\"true\"\n  app:default_artwork=\"@drawable/default_art\"\n```\n"
  },
  {
    "path": "README_CN.md",
    "content": "![logo](./images/default_art.png)\n# ExoVideoView\n![demo](./images/demo.gif)\n\nExoVideoView 是一款基于[ExoPlayer](https://github.com/google/ExoPlayer)开发的视频播放器.\n\n**ExoVideoView可以做什么**\n    1.自动处理音频焦点。\n    2.根据传感器自动处理方向。\n    3.手势支持。\n    4.多清晰度选择支持。\n    5.为控制器添加自定义布局.\n    6.调整显示大小。\n    7.自定义controller。\n    8.支持调整控件的可见性。\n## 使用 ExoVideoView\n### 1.依赖\n最简单的方式是加入gradle依赖。请确认在工程的build.gradle中添加了JCenter和google()。\n```\nallprojects {\n\t\trepositories {\n\t\t\t...\n\t\t\tmaven { url 'https://jitpack.io' }\n\t\t}\n\t}\n```\n然后在你的项目中添加如下代码：\n```\nimplementation 'com.github.JarvanMo:ExoVideoView:2.1.6'\n```\n### 2.在xml中定义\n在xml中使用 ExoVideoView:\n```xml\n<com.jarvanmo.exoplayerview.ui.ExoVideoView\n     android:id=\"@+id/videoView\"\n     android:layout_width=\"match_parent\"\n     android:layout_height=\"300dp\"/>\n```\n### 3.在java代码中\nExoVideoView 提供了内建```Player```：\n```java\nSimpleMediaSource mediaSource = new SimpleMediaSource(url);//也支持uri\nvideoView.play(mediaSource);\nvideoView.play(mediaSource,where);//play from a particular position\n```\n也可以使用自义的Player:\n```java\nvideoView.setPlayer(player);\n```\n提示:不要忘记释放ExoPlayer:\n```java\nvideoView.releasePlayer();\n```\n详情请移步[demo]().\n\n### 3.方向管理\nExoVideoView 可以自动处理方向问题，前提是为ExoVideoView设置一个OrientationListener:\n```java\n    videoView.setOrientationListener(orientation -> {\n            if (orientation == SENSOR_PORTRAIT) {\n                //do something\n            } else if (orientation == SENSOR_LANDSCAPE) {\n                //do something\n            }\n        });\n```\n提示：当ExoVideoView自动处理方向问题时，如果在Controller中的context是Activity,那么系统会调用\n```activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)``` or ```activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);```\n全屏事件处理也是如此。\n### 4返回管理\n首先,重写onKeyDown:\n```java\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            return videoView.onKeyDown(keyCode, event);\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n```\n为ExoVideoView设置监听:\n```java\n    videoView.setBackListener((view, isPortrait) -> {\n            if (isPortrait) {\n              //do something\n            }\n            return false;\n        });\n```\n如果返回值是 ```true```, 系统后续动作会被中断.否则，ExoVideoView会自动处理方向，并且会回调```OrientationLister.onOrientationChange()``` .\n## 高级\n### 1.多清晰度支持\nExoVideoView 内置清晰度选择器.如果开启发多清晰度并添加了多清晰度，内置清晰度选择器将被加入```overlayFrameLayout```.\n```java\n        List<ExoMediaSource.Quality> qualities = new ArrayList<>();\n        ExoMediaSource.Quality quality =new SimpleQuality(quality,mediaSource.url());\n        qualities.add(quality);\n        mediaSource.setQualities(qualities);\n```\n\n### 2.Controller显示模式\n```ExoVideoPlaybackController``` 被分为四个部分:\n```\n1.Top\n2.Top Landscape\n3.Bottom\n4.Bottom Landscape\n```\n每一部分都可以被显示或隐藏:\n```xml\n app:controller_display_mode=\"all|none|top|top_landscape|bottom|bottom_landscape\"\n```\n在java中:\n```java\n  videoView.setControllerDisplayMode(mode);\n```\n\n### 3.为controller添加控件\n```ExoVideoPlaybackController``` 允许在java代码中添加控件.\n```java\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP, view);\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP_LANDSCAPE, view);\n  videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_BOTTOM_LANDSCAPE, view);\n```\n### 4.使用自定义controller\n```exo_video_playback_control_view.xml```是允许自定义的。其中一些属性在```ExoVideoPlaybackControlView```有定义。具体可看源码。\n```xml\napp:controller_layout_id=\"@layout/my_controller\"\n```\n### 5.更改控件可见性\n有些时候我们可能不太喜欢返回，所以就让我们隐藏起来吧：\n```java\nvideoView.changeWidgetVisibility(R.id.exo_player_controller_back,View.INVISIBLE);\n```\n获取更多可隐藏的控件,参看 [ids_in_controller](./exoplayerview/src/main/res/values/ids.xml);\n> 注意：这么做可能比较危险，我不敢保证隐藏以后的效果。\n## 其他\n\n```\n  app:controller_background=\"@android:color/holo_orange_dark\"\n  app:use_artwork=\"true\"\n  app:default_artwork=\"@drawable/default_art\"\n```\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.0.1'\n//        classpath 'com.novoda:bintray-release:0.9.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n\n    }\n\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\next {\n    compileSdkVersion = 29\n    buildToolsVersion = \"26.0.2\"\n    minSdkVersion = 17\n    targetSdkVersion = 27\n\n    androidSupportVersion = '27.1.1'\n}\n\n\n//tasks.getByPath(\":exoplayerview:javadocRelease\").enabled = false"
  },
  {
    "path": "demo/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "demo/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    defaultConfig {\n        applicationId \"com.jarvanmo.demo\"\n        minSdkVersion 17\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        vectorDrawables.useSupportLibrary = true\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        abortOnError false\n    }\n\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    implementation 'androidx.appcompat:appcompat:1.0.2'\n    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'\n    testImplementation 'junit:junit:4.12'\n    implementation project(':exoplayerview')\n//    compile 'com.jarvanmo:exoplayerview:0.0.1'\n//    implementation 'com.google.android.exoplayer:exoplayer:2.7.3'\n    compileOnly 'androidx.recyclerview:recyclerview:1.0.0'\n}\n"
  },
  {
    "path": "demo/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /home/mo/Android/Sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class getDisplayName to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file getDisplayName.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "demo/src/androidTest/java/com/jarvanmo/demo/ExampleInstrumentedTest.java",
    "content": "package com.jarvanmo.demo;\n\nimport android.content.Context;\nimport androidx.test.InstrumentationRegistry;\nimport androidx.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.jarvanmo.demo\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "demo/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.jarvanmo.demo\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\"\n            android:theme=\"@style/FullscreenTheme\">\n\n        </activity>\n        <activity\n            android:name=\".SimpleVideoViewActivity\"\n            android:configChanges=\"keyboardHidden|orientation|screenSize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "demo/src/main/java/com/jarvanmo/demo/MainActivity.java",
    "content": "package com.jarvanmo.demo;\n\nimport android.graphics.Color;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.Button;\n\nimport com.google.android.exoplayer2.ui.AspectRatioFrameLayout;\nimport com.jarvanmo.exoplayerview.media.ExoMediaSource;\nimport com.jarvanmo.exoplayerview.media.SimpleMediaSource;\nimport com.jarvanmo.exoplayerview.media.SimpleQuality;\nimport com.jarvanmo.exoplayerview.ui.ExoVideoView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;\n\npublic class MainActivity extends AppCompatActivity {\n\n\n    private ExoVideoView videoView;\n    private Button modeFit;\n    private Button modeNone;\n    private Button modeHeight;\n    private Button modeWidth;\n    private Button modeZoom;\n    private View wrapper;\n    private Button play;\n    private View contentView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        contentView = findViewById(R.id.activity_main);\n\n        videoView = findViewById(R.id.videoView);\n        modeFit = findViewById(R.id.mode_fit);\n        modeNone = findViewById(R.id.mode_none);\n        modeHeight = findViewById(R.id.mode_height);\n        modeWidth = findViewById(R.id.mode_width);\n        modeZoom = findViewById(R.id.mode_zoom);\n        wrapper = findViewById(R.id.wrapper);\n        play = findViewById(R.id.changeMode);\n\n        videoView.setBackListener((view, isPortrait) -> {\n            if (isPortrait) {\n                finish();\n            }\n            return false;\n        });\n\n\n//\n//\n\n\n        videoView.setOrientationListener(orientation -> {\n            if (orientation == SENSOR_PORTRAIT) {\n                changeToPortrait();\n            } else if (orientation == SENSOR_LANDSCAPE) {\n                changeToLandscape();\n            }\n        });\n\n\n\n//\n\n//       final SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://video19.ifeng.com/video07/2013/11/11/281708-102-007-1138.mp4\");\n//        SimpleMediaSource mediaSource  = new SimpleMediaSource(\"https://tungsten.aaplimg.com/VOD/bipbop_adv_example_v2/master.m3u8\");\n//        final SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://playertest.longtailvideo.com/adaptive/bipbop/gear4/prog_index.m3u8\");\n//        SimpleMediaSource mediaSource  = new SimpleMediaSource(\" https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8\");\n//       SimpleMediaSource mediaSource = new SimpleMediaSource(\"https://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8\");\n//        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://pullhlsbb8f2e48.live.126.net/live/7de213ebb3dc4db2aa2f32f3da0b028d/playlist.m3u8\");\n//        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://rotation.vod.zlive.cc/channel/1234.m3u8\");\n//        SimpleMediaSource mediaSource = new SimpleMediaSource(\"https://media.w3.org/2010/05/sintel/trailer.mp4\");\n        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://flv2.bn.netease.com/videolib3/1604/28/fVobI0704/SD/fVobI0704-mobile.mp4\");\n//        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://stream1.hnntv.cn/lywsgq/sd/live.m3u8\");\n\n        mediaSource.setDisplayName(\"VideoPlaying\");\n        List<ExoMediaSource.Quality> qualities = new ArrayList<>();\n        ExoMediaSource.Quality quality;\n\n        for (int i = 0; i < 6; i++) {\n            SpannableString spannableString = new SpannableString(\"Quality\" + i);\n            if (i % 2 == 0) {\n                ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.YELLOW);\n                spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n\n            } else {\n                ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.RED);\n                spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n            }\n\n            quality = new SimpleQuality(spannableString, mediaSource.uri());\n            qualities.add(quality);\n        }\n        mediaSource.setQualities(qualities);\n\n        videoView.play(mediaSource, false);\n        play.setOnClickListener(view -> {\n            videoView.play(mediaSource);\n            play.setVisibility(View.INVISIBLE);\n        });\n\n\n        modeFit.setOnClickListener(v -> videoView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT));\n        modeWidth.setOnClickListener(v -> videoView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH));\n        modeHeight.setOnClickListener(v -> videoView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT));\n        modeNone.setOnClickListener(v -> videoView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL));\n        modeZoom.setOnClickListener(v -> videoView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM));\n    }\n\n    private void changeToPortrait() {\n\n        WindowManager.LayoutParams attr = getWindow().getAttributes();\n//        attr.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        Window window = getWindow();\n        window.setAttributes(attr);\n        window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        wrapper.setVisibility(View.VISIBLE);\n    }\n\n\n    private void changeToLandscape() {\n        WindowManager.LayoutParams lp = getWindow().getAttributes();\n//        lp.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;\n        Window window = getWindow();\n        window.setAttributes(lp);\n        window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        wrapper.setVisibility(View.GONE);\n    }\n\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        if (Build.VERSION.SDK_INT > 23) {\n            videoView.resume();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        if ((Build.VERSION.SDK_INT <= 23)) {\n            videoView.resume();\n        }\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (Build.VERSION.SDK_INT <= 23) {\n            videoView.pause();\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        if (Build.VERSION.SDK_INT > 23) {\n            videoView.pause();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        videoView.releasePlayer();\n    }\n\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            return videoView.onKeyDown(keyCode, event);\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/jarvanmo/demo/SimpleVideoViewActivity.java",
    "content": "package com.jarvanmo.demo;\n\nimport android.content.res.Configuration;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.CheckBox;\nimport android.widget.Spinner;\nimport android.widget.Toast;\n\nimport com.jarvanmo.exoplayerview.extension.MultiQualitySelectorAdapter;\nimport com.jarvanmo.exoplayerview.media.ExoMediaSource;\nimport com.jarvanmo.exoplayerview.media.SimpleMediaSource;\nimport com.jarvanmo.exoplayerview.media.SimpleQuality;\nimport com.jarvanmo.exoplayerview.ui.ExoVideoPlaybackControlView;\nimport com.jarvanmo.exoplayerview.ui.ExoVideoView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;\n\npublic class SimpleVideoViewActivity extends AppCompatActivity {\n\n    private ExoVideoView videoView;\n    private View wrapper;\n    private final String[] modes = new String[]{\"RESIZE_MODE_FIT\", \"RESIZE_MODE_FIXED_WIDTH\"\n            , \"RESIZE_MODE_FIXED_HEIGHT\", \"RESIZE_MODE_FILL\", \"RESIZE_MODE_ZOOM\"};\n    private Spinner modeSpinner;\n    private ArrayAdapter<String> adapter;\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_simple_video_view);\n        wrapper = findViewById(R.id.wrapper);\n\n        initSpinner();\n        initControllerMode();\n        initVideoView();\n        initCustomViews();\n    }\n\n    private void initVideoView() {\n        videoView = findViewById(R.id.videoView);\n        videoView.setPortrait(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);\n        videoView.setBackListener((view, isPortrait) -> {\n            if (isPortrait) {\n                finish();\n            }\n            return false;\n        });\n\n        videoView.setOrientationListener(orientation -> {\n            if (orientation == SENSOR_PORTRAIT) {\n                changeToPortrait();\n            } else if (orientation == SENSOR_LANDSCAPE) {\n                changeToLandscape();\n            }\n        });\n\n//        videoView.setGestureEnabled(false);\n//\n//\n//        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://flv2.bn.netease.com/videolib3/1604/28/fVobI0704/SD/fVobI0704-mobile.mp4\");\n//        mediaSource.setDisplayName(\"Apple HLS\");\n\n        SimpleMediaSource mediaSource = new SimpleMediaSource(\"http://vfx.mtime.cn/Video/2019/03/12/mp4/190312083533415853.mp4\");\n        mediaSource.setDisplayName(\"Apple HLS\");\n\n        //demo only,not real multi quality, urls are the same actually\n        List<ExoMediaSource.Quality> qualities = new ArrayList<>();\n        ExoMediaSource.Quality quality;\n        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.YELLOW);\n        SpannableString spannableString = new SpannableString(\"1080p\");\n        spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        quality = new SimpleQuality(spannableString, mediaSource.uri());\n        qualities.add(quality);\n\n        spannableString = new SpannableString(\"720p\");\n        colorSpan = new ForegroundColorSpan(Color.LTGRAY);\n        spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        quality = new SimpleQuality(spannableString, mediaSource.uri());\n        qualities.add(quality);\n\n        mediaSource.setQualities(qualities);\n//        videoView.changeWidgetVisibility(R.id.exo_player_controller_back,View.INVISIBLE);\n        videoView.setMultiQualitySelectorNavigator(new MultiQualitySelectorAdapter.MultiQualitySelectorNavigator() {\n            @Override\n            public boolean onQualitySelected(ExoMediaSource.Quality quality) {\n                quality.setUri(Uri.parse(\"https://media.w3.org/2010/05/sintel/trailer.mp4\"));\n                return false;\n            }\n        });\n        videoView.play(mediaSource, false);\n\n    }\n\n    private void initSpinner() {\n        modeSpinner = findViewById(R.id.spinner);\n        adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);\n        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);\n        modeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                videoView.setResizeMode(position);\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n        adapter.addAll(modes);\n        modeSpinner.setAdapter(adapter);\n    }\n\n    private void initControllerMode() {\n        CheckBox all = findViewById(R.id.all);\n        CheckBox top = findViewById(R.id.top);\n        CheckBox topLandscape = findViewById(R.id.topLandscape);\n        CheckBox bottom = findViewById(R.id.bottom);\n        CheckBox bottomLandscape = findViewById(R.id.bottomLandscape);\n        CheckBox none = findViewById(R.id.none);\n        findViewById(R.id.applyControllerMode).setOnClickListener(v -> {\n            int mode = ExoVideoPlaybackControlView.CONTROLLER_MODE_NONE;\n            if (all.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_ALL;\n            }\n            if (top.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_TOP;\n            }\n\n            if (topLandscape.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_TOP_LANDSCAPE;\n            }\n            if (bottom.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_BOTTOM;\n            }\n            if (bottomLandscape.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_BOTTOM_LANDSCAPE;\n            }\n            if (none.isChecked()) {\n                mode |= ExoVideoPlaybackControlView.CONTROLLER_MODE_NONE;\n            }\n\n            videoView.setControllerDisplayMode(mode);\n            Toast.makeText(SimpleVideoViewActivity.this, \"change controller display mode\", Toast.LENGTH_SHORT).show();\n        });\n\n\n    }\n\n    private void initCustomViews() {\n        findViewById(R.id.addToTop).setOnClickListener(v -> {\n            View view = getLayoutInflater().inflate(R.layout.cutom_view_top, null, false);\n            videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP, view);\n        });\n\n        findViewById(R.id.addToTopLandscape).setOnClickListener(v -> {\n            View view = getLayoutInflater().inflate(R.layout.cutom_view_top_landscape, null, false);\n            videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_TOP_LANDSCAPE, view);\n        });\n\n\n        findViewById(R.id.addToBottomLandscape).setOnClickListener(v -> {\n            View view = getLayoutInflater().inflate(R.layout.cutom_view_bottom_landscape, null, false);\n            videoView.addCustomView(ExoVideoPlaybackControlView.CUSTOM_VIEW_BOTTOM_LANDSCAPE, view);\n        });\n    }\n\n    private void changeToPortrait() {\n\n        // WindowManager operation is not necessary\n        WindowManager.LayoutParams attr = getWindow().getAttributes();\n//        attr.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        Window window = getWindow();\n        window.setAttributes(attr);\n        window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n\n\n        wrapper.setVisibility(View.VISIBLE);\n    }\n\n\n    private void changeToLandscape() {\n\n        // WindowManager operation is not necessary\n\n        WindowManager.LayoutParams lp = getWindow().getAttributes();\n//        lp.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;\n        Window window = getWindow();\n        window.setAttributes(lp);\n        window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);\n        wrapper.setVisibility(View.GONE);\n    }\n\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        if (Build.VERSION.SDK_INT > 23) {\n            videoView.resume();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        if ((Build.VERSION.SDK_INT <= 23)) {\n            videoView.resume();\n        }\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (Build.VERSION.SDK_INT <= 23) {\n            videoView.pause();\n        }\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        if (Build.VERSION.SDK_INT > 23) {\n            videoView.pause();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        videoView.releasePlayer();\n    }\n\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            return videoView.onKeyDown(keyCode, event);\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n}\n"
  },
  {
    "path": "demo/src/main/res/drawable/ic_arrow_back_white_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\" />\n</vector>\n"
  },
  {
    "path": "demo/src/main/res/drawable/ic_error_white_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z\" />\n</vector>\n"
  },
  {
    "path": "demo/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/activity_main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.jarvanmo.demo.MainActivity\">\n\n\n    <com.jarvanmo.exoplayerview.ui.ExoVideoView\n        android:id=\"@+id/videoView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1.5\"\n        app:controller_display_mode=\"all\"\n        app:resize_mode=\"fill\"\n        app:default_artwork=\"@drawable/default_art\"\n        app:use_artwork=\"true\" />\n\n    <androidx.constraintlayout.ConstraintLayout\n        android:id=\"@+id/wrapper\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:gravity=\"center\">\n\n        <Button\n            android:id=\"@+id/changeMode\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"play\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <Button\n            android:id=\"@+id/mode_none\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:text=\"mode_fill\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/changeMode\" />\n\n        <Button\n            android:id=\"@+id/mode_fit\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:text=\"mode_fit\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/mode_none\"\n            app:layout_constraintStart_toEndOf=\"@+id/mode_none\"\n            app:layout_constraintTop_toTopOf=\"@+id/mode_none\" />\n\n        <Button\n            android:id=\"@+id/mode_width\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"16dp\"\n            android:text=\"mode_fix_width\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/mode_height\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/mode_height\" />\n\n        <Button\n            android:id=\"@+id/mode_height\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:text=\"mode_fix_height\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/mode_none\" />\n\n        <Button\n            android:id=\"@+id/mode_zoom\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:text=\"mode_zoom\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/mode_fit\"\n            app:layout_constraintStart_toEndOf=\"@+id/mode_fit\"\n            app:layout_constraintTop_toTopOf=\"@+id/mode_fit\" />\n    </androidx.constraintlayout.ConstraintLayout>\n\n\n</LinearLayout>\n"
  },
  {
    "path": "demo/src/main/res/layout/activity_simple_video_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/activity_main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.jarvanmo.demo.MainActivity\">\n\n    <com.jarvanmo.exoplayerview.ui.ExoVideoView\n        android:id=\"@+id/videoView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1.5\" />\n\n    <ScrollView\n        android:id=\"@+id/wrapper\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"2\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/changeMode\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"16dp\"\n                android:layout_marginTop=\"16dp\"\n                android:text=\"change resize mode\"\n                android:textColor=\"@android:color/holo_orange_dark\"\n                android:textSize=\"18sp\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <Spinner\n                android:id=\"@+id/spinner\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"16dp\"\n                android:layout_marginStart=\"16dp\"\n                android:layout_marginTop=\"8dp\"\n                android:spinnerMode=\"dialog\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/changeMode\" />\n\n            <CheckBox\n                android:id=\"@+id/all\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:checked=\"true\"\n                android:text=\"All\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/controllerMode\" />\n\n            <TextView\n                android:id=\"@+id/controllerMode\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"16dp\"\n                android:text=\"Controller Display Mode\"\n                android:textColor=\"@android:color/holo_orange_dark\"\n                android:textSize=\"18sp\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/spinner\" />\n\n            <TextView\n                android:id=\"@+id/applyControllerMode\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"8dp\"\n                android:text=\"apply\"\n                android:textSize=\"18sp\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"@+id/controllerMode\" />\n\n            <CheckBox\n                android:id=\"@+id/top\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"Top\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/all\" />\n\n            <CheckBox\n                android:id=\"@+id/topLandscape\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"Top Landscape\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/top\" />\n\n            <CheckBox\n                android:id=\"@+id/bottom\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"Bottom\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/topLandscape\" />\n\n            <CheckBox\n                android:id=\"@+id/bottomLandscape\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"Bottom Lanscape\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/bottom\" />\n\n            <CheckBox\n                android:id=\"@+id/none\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:text=\"None\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/bottomLandscape\" />\n\n            <TextView\n                android:id=\"@+id/customViews\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:text=\"add custom view to controller\"\n                android:textColor=\"@android:color/holo_orange_dark\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/none\" />\n\n            <TextView\n                android:id=\"@+id/addToTop\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:drawableEnd=\"@drawable/ic_playlist_add_green_a700_24dp\"\n                android:drawablePadding=\"5dp\"\n                android:text=\"add view in to top controller\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/customViews\" />\n\n            <TextView\n                android:id=\"@+id/addToTopLandscape\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:drawableEnd=\"@drawable/ic_playlist_add_green_a700_24dp\"\n                android:drawablePadding=\"5dp\"\n                android:text=\"add view in to top(landscape) controller\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/addToTop\" />\n\n            <TextView\n                android:id=\"@+id/addToBottomLandscape\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:drawableEnd=\"@drawable/ic_playlist_add_green_a700_24dp\"\n                android:drawablePadding=\"5dp\"\n                android:text=\"add view in to bottom(landscape) controller\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/addToTopLandscape\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "demo/src/main/res/layout/cutom_view_bottom_landscape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_message_white_24dp\" />\n</androidx.constraintlayout.ConstraintLayout>"
  },
  {
    "path": "demo/src/main/res/layout/cutom_view_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_share_white_24dp\" />\n</androidx.constraintlayout.ConstraintLayout>"
  },
  {
    "path": "demo/src/main/res/layout/cutom_view_top_landscape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_more_vert_white_24dp\" />\n</androidx.constraintlayout.ConstraintLayout>"
  },
  {
    "path": "demo/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n\n    <color name=\"black_overlay\">#66000000</color>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">ExoPlayerView</string>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/default_playback_background</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"FullscreenTheme\" parent=\"AppTheme\">\n        <item name=\"android:windowActionBarOverlay\">true</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "demo/src/test/java/com/jarvanmo/demo/ExampleUnitTest.java",
    "content": "package com.jarvanmo.demo;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "exoplayerview/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "exoplayerview/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 36\n        versionName \"2.2.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        vectorDrawables.useSupportLibrary = true\n    }\n\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n\n    lintOptions {\n        abortOnError false\n    }\n\n\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n}\n\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n\n    implementation 'androidx.appcompat:appcompat:1.1.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8'\n    testImplementation 'junit:junit:4.12'\n\n    api 'com.google.android.exoplayer:exoplayer:2.11.0'\n\n\n    implementation 'androidx.recyclerview:recyclerview:1.1.0'\n}\n\n//tasks.withType(Javadoc) {\n//    options.addStringOption('Xdoclint:none', '-quiet')\n//    options.addStringOption('encoding', 'UTF-8')\n//    options.addStringOption('charSet', 'UTF-8')\n//}"
  },
  {
    "path": "exoplayerview/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /home/mo/Android/Sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class getDisplayName to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "exoplayerview/src/androidTest/java/com/jarvanmo/exoplayerview/ExampleInstrumentedTest.java",
    "content": "package com.jarvanmo.exoplayerview;\n\nimport android.content.Context;\nimport androidx.test.InstrumentationRegistry;\nimport androidx.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.jarvanmo.exoplayerview.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "exoplayerview/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.jarvanmo.exoplayerview\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\">\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/ads/ExoAdsLoader.java",
    "content": "package com.jarvanmo.exoplayerview.ads;\n\nimport android.view.ViewGroup;\n\nimport com.google.android.exoplayer2.ExoPlayer;\nimport com.google.android.exoplayer2.Player;\nimport com.google.android.exoplayer2.source.ads.AdsLoader;\n\nimport java.io.IOException;\n\nimport androidx.annotation.Nullable;\n\n/**\n * Created by mo on 17-11-30.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class ExoAdsLoader extends Player.DefaultEventListener implements AdsLoader {\n\n    @Override\n    public void setSupportedContentTypes(int... contentTypes) {\n\n    }\n\n    @Override\n    public void start(EventListener eventListener, AdViewProvider adViewProvider) {\n\n    }\n\n    @Override\n    public void stop() {\n\n    }\n\n\n    @Override\n    public void setPlayer(@Nullable Player player) {\n\n    }\n\n    @Override\n    public void release() {\n\n    }\n\n    @Override\n    public void handlePrepareError(int adGroupIndex, int adIndexInAdGroup, IOException exception) {\n\n    }\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/extension/MultiQualitySelectorAdapter.java",
    "content": "package com.jarvanmo.exoplayerview.extension;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.jarvanmo.exoplayerview.R;\nimport com.jarvanmo.exoplayerview.media.ExoMediaSource;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by mo on 18-2-8.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class MultiQualitySelectorAdapter extends RecyclerView.Adapter<MultiQualitySelectorAdapter.MultiQualitySelectorViewHolder> {\n\n    public interface MultiQualitySelectorNavigator {\n\n        /***\n         * @return interrupted if true\n         * */\n        boolean onQualitySelected(ExoMediaSource.Quality quality);\n    }\n\n\n    public interface VisibilityCallback {\n        void shouldChangeVisibility(int visibility);\n    }\n\n    private List<ExoMediaSource.Quality> qualities = new ArrayList<>();\n    private MultiQualitySelectorNavigator navigator;\n\n\n    public MultiQualitySelectorAdapter(List<ExoMediaSource.Quality> qualities, MultiQualitySelectorNavigator navigator) {\n        this.qualities.addAll(qualities);\n        this.navigator = navigator;\n    }\n\n    @NonNull\n    @Override\n    public MultiQualitySelectorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {\n        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_quality, parent, false);\n        return new MultiQualitySelectorViewHolder(view);\n    }\n\n    @Override\n    public void onBindViewHolder(MultiQualitySelectorViewHolder holder, int position) {\n        holder.rootView.setOnClickListener(view -> navigator.onQualitySelected(qualities.get(position)));\n        holder.qualityDes.setText(qualities.get(position).getDisplayName());\n    }\n\n    @Override\n    public int getItemCount() {\n        return qualities.size();\n    }\n\n    class MultiQualitySelectorViewHolder extends RecyclerView.ViewHolder {\n        View rootView;\n        TextView qualityDes;\n\n        MultiQualitySelectorViewHolder(View itemView) {\n            super(itemView);\n            rootView = itemView;\n            qualityDes = itemView.findViewById(R.id.qualityDes);\n        }\n    }\n\n}"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/gesture/OnVideoGestureChangeListener.java",
    "content": "package com.jarvanmo.exoplayerview.gesture;\n\nimport androidx.annotation.IntDef;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * Created by mo on 18-2-2.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic interface OnVideoGestureChangeListener {\n\n    int VOLUME_CHANGED_REDUCTION = -1;\n    int VOLUME_CHANGED_MUTE = VOLUME_CHANGED_REDUCTION + 1;\n    int VOLUME_CHANGED_INCREMENT = VOLUME_CHANGED_MUTE + 1;\n\n\n    @IntDef({VOLUME_CHANGED_REDUCTION, VOLUME_CHANGED_MUTE, VOLUME_CHANGED_INCREMENT})\n    @Retention(RetentionPolicy.SOURCE)\n    @interface VolumeChangeType {\n\n    }\n\n\n    void onVolumeChanged(int range, @VolumeChangeType int type);\n\n    void onBrightnessChanged(int brightnessPercent);\n\n    void onNoGesture();\n\n    void onShowSeekSize(long seekSize, boolean fastForward);\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/gesture/VideoGesture.java",
    "content": "package com.jarvanmo.exoplayerview.gesture;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.media.AudioManager;\nimport android.provider.Settings;\nimport androidx.annotation.NonNull;\nimport android.util.DisplayMetrics;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.WindowManager;\n\nimport com.google.android.exoplayer2.Player;\nimport com.google.android.exoplayer2.Timeline;\nimport com.jarvanmo.exoplayerview.ui.ExoVideoPlaybackControlView;\nimport com.jarvanmo.exoplayerview.util.Permissions;\n\nimport static android.content.Context.AUDIO_SERVICE;\nimport static com.jarvanmo.exoplayerview.gesture.OnVideoGestureChangeListener.VOLUME_CHANGED_INCREMENT;\nimport static com.jarvanmo.exoplayerview.gesture.OnVideoGestureChangeListener.VOLUME_CHANGED_MUTE;\nimport static com.jarvanmo.exoplayerview.gesture.OnVideoGestureChangeListener.VOLUME_CHANGED_REDUCTION;\n\n/**\n * Created by mo on 18-2-2.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class VideoGesture implements View.OnTouchListener {\n\n    private final Context context;\n    private final OnVideoGestureChangeListener onVideoGestureChangeListener;\n    private final ExoVideoPlaybackControlView.PlayerAccessor playerAccessor;\n\n    private final Timeline.Window window;\n\n    //Touch Events\n    private static final int TOUCH_NONE = 0;\n    private static final int TOUCH_VOLUME = 1;\n    private static final int TOUCH_BRIGHTNESS = 2;\n    private static final int TOUCH_SEEK = 3;\n\n    //touch\n    private int mTouchAction = TOUCH_NONE;\n    private int mSurfaceYDisplayRange;\n    private float mInitTouchY;\n    private float touchX = -1f;\n    private float touchY = -1f;\n\n\n    //Volume\n    private AudioManager mAudioManager;\n    private int mAudioMax;\n    private float mVol;\n\n    // Brightness\n    private boolean mIsFirstBrightnessGesture = true;\n\n    private boolean enabled = true;\n\n    public VideoGesture(Context context, OnVideoGestureChangeListener onVideoGestureChangeListener, @NonNull ExoVideoPlaybackControlView.PlayerAccessor playerAccessor) {\n        this.context = context;\n        this.onVideoGestureChangeListener = onVideoGestureChangeListener;\n        mAudioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);\n        this.playerAccessor = playerAccessor;\n        window = new Timeline.Window();\n\n        initVol();\n    }\n\n\n    private void initVol() {\n\n            /* Services and miscellaneous */\n        mAudioManager = (AudioManager) context.getApplicationContext().getSystemService(AUDIO_SERVICE);\n        mAudioMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);\n\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        if(!enabled){\n            return  false;\n        }\n        return dispatchCenterWrapperTouchEvent(event);\n\n    }\n\n    private boolean dispatchCenterWrapperTouchEvent(MotionEvent event) {\n\n\n        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);\n\n        DisplayMetrics screen = new DisplayMetrics();\n        wm.getDefaultDisplay().getMetrics(screen);\n\n        if (mSurfaceYDisplayRange == 0) {\n            mSurfaceYDisplayRange = Math.min(screen.widthPixels, screen.heightPixels);\n        }\n\n        float x_changed, y_changed;\n        if (touchX != -1f && touchY != -1f) {\n            y_changed = event.getRawY() - touchY;\n            x_changed = event.getRawX() - touchX;\n        } else {\n            x_changed = 0f;\n            y_changed = 0f;\n        }\n\n//        Log.e(\"tag\",\"x_c=\" + x_changed + \"screen_x =\" + screen.xdpi +\" screen_rawx\" + event.getRawX());\n        float coef = Math.abs(y_changed / x_changed);\n        float xgesturesize = (((event.getRawX() - touchX) / screen.xdpi) * 2.54f);//2.54f\n\n\n        float delta_y = Math.max(1f, (Math.abs(mInitTouchY - event.getRawY()) / screen.xdpi + 0.5f) * 2f);\n\n        switch (event.getAction()) {\n\n            case MotionEvent.ACTION_DOWN:\n                mTouchAction = TOUCH_NONE;\n                touchX = event.getRawX();\n                mVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n                touchY = mInitTouchY = event.getRawY();\n                break;\n\n            case MotionEvent.ACTION_MOVE:\n\n                if (mTouchAction != TOUCH_SEEK && coef > 2) {\n                    if (Math.abs(y_changed / mSurfaceYDisplayRange) < 0.05) {\n                        return false;\n                    }\n\n                    touchX = event.getRawX();\n                    touchY = event.getRawY();\n\n\n                    if ((int) touchX > (4 * screen.widthPixels / 7)) {\n                        doVolumeTouch(y_changed);\n\n                    }\n                    // Brightness (Up or Down - Left side)\n                    if ((int) touchX < (3 * screen.widthPixels / 7)) {\n                        doBrightnessTouch(y_changed);\n                    }\n\n                } else {\n                    doSeekTouch(Math.round(delta_y), xgesturesize, false);\n                }\n\n                break;\n\n            case MotionEvent.ACTION_UP:\n                if (mTouchAction == TOUCH_SEEK) {\n                    doSeekTouch(Math.round(delta_y), xgesturesize, true);\n                }\n\n                if (mTouchAction != TOUCH_NONE) {\n                    hideCenterInfo();\n                }\n\n                touchX = -1f;\n                touchY = -1f;\n                break;\n            default:\n                break;\n        }\n\n\n        return mTouchAction != TOUCH_NONE;\n    }\n\n    private void doVolumeTouch(float y_changed) {\n        if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_VOLUME) {\n            return;\n        }\n\n        int oldVol = (int) mVol;\n        mTouchAction = TOUCH_VOLUME;\n        float delta = -((y_changed / mSurfaceYDisplayRange) * mAudioMax);\n        mVol += delta;\n        int vol = (int) Math.min(Math.max(mVol, 0), mAudioMax);\n        if (delta != 0f) {\n            setAudioVolume(vol, vol > oldVol);\n        }\n    }\n\n    private void setAudioVolume(int vol, boolean isUp) {\n        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0);\n\n        /* Since android 4.3, the safe volume warning dialog is displayed only with the FLAG_SHOW_UI flag.\n         * We don't want to always show the default UI volume, so show it only when volume is not set. */\n        int newVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);\n        if (vol != newVol) {\n            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, AudioManager.FLAG_SHOW_UI);\n        }\n\n        mTouchAction = TOUCH_VOLUME;\n        vol = vol * 100 / mAudioMax;\n        int type;\n        if (newVol == 0) {\n            type = VOLUME_CHANGED_MUTE;\n        } else if (isUp) {\n            type = VOLUME_CHANGED_INCREMENT;\n        } else {\n            type = VOLUME_CHANGED_REDUCTION;\n        }\n\n        onVolumeChanged(vol, type);\n    }\n\n    private void onVolumeChanged(int range, @OnVideoGestureChangeListener.VolumeChangeType int type) {\n        if (onVideoGestureChangeListener != null) {\n            onVideoGestureChangeListener.onVolumeChanged(range, type);\n        }\n    }\n\n\n    private void doBrightnessTouch(float y_changed) {\n        if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_BRIGHTNESS) {\n            return;\n        }\n\n        mTouchAction = TOUCH_BRIGHTNESS;\n        if (mIsFirstBrightnessGesture) {\n            initBrightnessTouch();\n        }\n\n        mTouchAction = TOUCH_BRIGHTNESS;\n//\n        // Set delta : 2f is arbitrary for now, it possibly will change in the future\n        float delta = -y_changed / mSurfaceYDisplayRange;\n        changeBrightness(delta);\n    }\n\n    private void changeBrightness(float delta) {\n        // Estimate and adjust Brightness\n        if (!(context instanceof Activity)) {\n            return;\n        }\n        Activity activity = (Activity) context;\n\n\n        WindowManager.LayoutParams lp = activity.getWindow().getAttributes();\n        float brightness = Math.min(Math.max(lp.screenBrightness + delta, 0.01f), 1f);\n\n        lp.screenBrightness = brightness;\n        // Set Brightness\n        activity.getWindow().setAttributes(lp);\n\n        brightness = Math.round(brightness * 100);\n\n        onBrightnessChanged((int) brightness);\n\n    }\n\n    private void onBrightnessChanged(int brightnessPercent) {\n        if (onVideoGestureChangeListener != null) {\n            onVideoGestureChangeListener.onBrightnessChanged(brightnessPercent);\n        }\n    }\n\n    private void doSeekTouch(int coef, float gesturesize, boolean seek) {\n        if (coef == 0) {\n            coef = 1;\n        }\n\n\n        // No seek action if coef > 0.5 and gesturesize < 1cm\n\n        if (Math.abs(gesturesize) < 1 || !canSeek()) {\n            return;\n        }\n\n\n        if (mTouchAction != TOUCH_NONE && mTouchAction != TOUCH_SEEK) {\n            return;\n        }\n\n\n        mTouchAction = TOUCH_SEEK;\n        Player player = playerAccessor.attachPlayer();\n        long length = player.getDuration();\n        long time = player.getCurrentPosition();\n\n        // Size of the jump, 10 minutes max (600000), with a bi-cubic progression, for a 8cm gesture\n        int jump = (int) ((Math.signum(gesturesize) * ((600000 * Math.pow((gesturesize / 8), 4)) + 3000)) / coef);\n\n        // Adjust the jump\n        if ((jump > 0) && ((time + jump) > length)) {\n            jump = (int) (length - time);\n        }\n\n\n        if ((jump < 0) && ((time + jump) < 0)) {\n            jump = (int) -time;\n        }\n\n        //Jump !\n//        if (seek && length > 0) {\n//            jump(time + jump);\n//        }\n\n        if (length > 0) {\n            //Show the jump's size\n            seekAndShowJump(seek, time + jump, jump > 0);\n        }\n    }\n\n    private void seekAndShowJump(boolean seek, long jumpSize, boolean isFastForward) {\n        if (onVideoGestureChangeListener != null) {\n            onVideoGestureChangeListener.onShowSeekSize(jumpSize, isFastForward);\n        }\n    }\n\n    private void hideCenterInfo() {\n        if (onVideoGestureChangeListener != null) {\n            onVideoGestureChangeListener.onNoGesture();\n        }\n    }\n\n\n    private void initBrightnessTouch() {\n        if (!(context instanceof Activity)) {\n            return;\n        }\n        Activity activity = (Activity) context;\n\n        WindowManager.LayoutParams lp = activity.getWindow().getAttributes();\n        float brightnesstemp = lp.screenBrightness != -1f ? lp.screenBrightness : 0.6f;\n        // Initialize the layoutParams screen brightness\n        try {\n            if (Settings.System.getInt(activity.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {\n                if (!Permissions.canWriteSettings(activity)) {\n                    return;\n                }\n                Settings.System.putInt(activity.getContentResolver(),\n                        Settings.System.SCREEN_BRIGHTNESS_MODE,\n                        Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);\n//                restoreAutoBrightness = android.provider.Settings.System.getInt(activity.getContentResolver(),\n//                        android.provider.Settings.System.SCREEN_BRIGHTNESS) / 255.0f;\n            } else if (brightnesstemp == 0.6f) {\n                brightnesstemp = android.provider.Settings.System.getInt(activity.getContentResolver(),\n                        android.provider.Settings.System.SCREEN_BRIGHTNESS) / 255.0f;\n            }\n        } catch (Settings.SettingNotFoundException e) {\n            e.printStackTrace();\n        }\n        lp.screenBrightness = brightnesstemp;\n        activity.getWindow().setAttributes(lp);\n        mIsFirstBrightnessGesture = false;\n    }\n\n    private boolean canSeek() {\n\n        Player player = playerAccessor.attachPlayer();\n\n        Timeline timeline = player != null ? player.getCurrentTimeline() : null;\n        boolean haveNonEmptyTimeline = timeline != null && !timeline.isEmpty();\n        boolean isSeekable = false;\n        if (haveNonEmptyTimeline) {\n            int windowIndex = player.getCurrentWindowIndex();\n            timeline.getWindow(windowIndex, window);\n            isSeekable = window.isSeekable;\n        }\n\n        return isSeekable;\n    }\n\n    public void enable() {\n        enabled = true;\n    }\n\n    public void disable() {\n        enabled = false;\n    }\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/EventLogger.java",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jarvanmo.exoplayerview.media;\n\nimport androidx.annotation.Nullable;\nimport android.util.Log;\nimport android.view.Surface;\n\nimport com.google.android.exoplayer2.ExoPlaybackException;\nimport com.google.android.exoplayer2.Format;\nimport com.google.android.exoplayer2.PlaybackParameters;\nimport com.google.android.exoplayer2.Player;\nimport com.google.android.exoplayer2.Timeline;\nimport com.google.android.exoplayer2.audio.AudioRendererEventListener;\nimport com.google.android.exoplayer2.decoder.DecoderCounters;\nimport com.google.android.exoplayer2.drm.DefaultDrmSessionManager;\nimport com.google.android.exoplayer2.metadata.Metadata;\nimport com.google.android.exoplayer2.metadata.MetadataOutput;\nimport com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;\nimport com.google.android.exoplayer2.source.ExtractorMediaSource;\nimport com.google.android.exoplayer2.source.MediaSource;\nimport com.google.android.exoplayer2.source.MediaSourceEventListener;\nimport com.google.android.exoplayer2.source.TrackGroupArray;\nimport com.google.android.exoplayer2.trackselection.MappingTrackSelector;\nimport com.google.android.exoplayer2.trackselection.TrackSelectionArray;\nimport com.google.android.exoplayer2.upstream.DataSpec;\nimport com.google.android.exoplayer2.video.VideoRendererEventListener;\n\nimport java.io.IOException;\n\n/**\n * Logs player events using {@link Log}.\n */\n/* package */ final class EventLogger implements Player.EventListener, MetadataOutput,\n        AudioRendererEventListener, VideoRendererEventListener, MediaSourceEventListener {\n\n    public EventLogger(MappingTrackSelector trackSelector) {\n    }\n\n    @Override\n    public void onTimelineChanged(Timeline timeline, int reason) {\n\n    }\n\n    @Override\n    public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {\n\n    }\n\n    @Override\n    public void onLoadingChanged(boolean isLoading) {\n\n    }\n\n    @Override\n    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {\n\n    }\n\n    @Override\n    public void onRepeatModeChanged(int repeatMode) {\n\n    }\n\n    @Override\n    public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {\n\n    }\n\n    @Override\n    public void onPlayerError(ExoPlaybackException error) {\n\n    }\n\n    @Override\n    public void onPositionDiscontinuity(int reason) {\n\n    }\n\n    @Override\n    public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {\n\n    }\n\n    @Override\n    public void onSeekProcessed() {\n\n    }\n\n    @Override\n    public void onAudioEnabled(DecoderCounters counters) {\n\n    }\n\n    @Override\n    public void onAudioSessionId(int audioSessionId) {\n\n    }\n\n    @Override\n    public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {\n\n    }\n\n    @Override\n    public void onAudioInputFormatChanged(Format format) {\n\n    }\n\n    @Override\n    public void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {\n\n    }\n\n    @Override\n    public void onAudioDisabled(DecoderCounters counters) {\n\n    }\n\n\n    @Override\n    public void onMetadata(Metadata metadata) {\n\n    }\n\n\n    @Override\n    public void onVideoEnabled(DecoderCounters counters) {\n\n    }\n\n    @Override\n    public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {\n\n    }\n\n    @Override\n    public void onVideoInputFormatChanged(Format format) {\n\n    }\n\n    @Override\n    public void onDroppedFrames(int count, long elapsedMs) {\n\n    }\n\n    @Override\n    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {\n\n    }\n\n    @Override\n    public void onRenderedFirstFrame(Surface surface) {\n\n    }\n\n    @Override\n    public void onVideoDisabled(DecoderCounters counters) {\n\n    }\n\n    @Override\n    public void onMediaPeriodCreated(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {\n\n    }\n\n    @Override\n    public void onMediaPeriodReleased(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {\n\n    }\n\n    @Override\n    public void onLoadStarted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {\n\n    }\n\n    @Override\n    public void onLoadCompleted(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {\n\n    }\n\n    @Override\n    public void onLoadCanceled(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {\n\n    }\n\n    @Override\n    public void onLoadError(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData, IOException error, boolean wasCanceled) {\n\n    }\n\n    @Override\n    public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {\n\n    }\n\n    @Override\n    public void onUpstreamDiscarded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {\n\n    }\n\n    @Override\n    public void onDownstreamFormatChanged(int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {\n\n    }\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/ExoMediaSource.java",
    "content": "package com.jarvanmo.exoplayerview.media;\n\nimport android.net.Uri;\n\nimport java.util.List;\n\n/**\n * Created by mo on 18-1-11.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic interface ExoMediaSource {\n\n    interface Quality {\n        CharSequence getDisplayName();\n\n        Uri getUri();\n\n        void setUri(Uri uri);\n\n        void setDisplayName(CharSequence displayName);\n\n        void setQuality(String quality);\n\n        String getQuality();\n\n    }\n\n    Uri uri();\n\n    String name();\n\n    List<Quality> qualities();\n\n    String extension();\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/MediaSourceCreator.java",
    "content": "package com.jarvanmo.exoplayerview.media;\n\nimport android.content.Context;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.text.TextUtils;\n\nimport com.google.android.exoplayer2.C;\nimport com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;\nimport com.google.android.exoplayer2.source.ExtractorMediaSource;\nimport com.google.android.exoplayer2.source.MediaSource;\nimport com.google.android.exoplayer2.source.ProgressiveMediaSource;\nimport com.google.android.exoplayer2.source.dash.DashMediaSource;\nimport com.google.android.exoplayer2.source.hls.HlsMediaSource;\nimport com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;\nimport com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;\nimport com.google.android.exoplayer2.trackselection.DefaultTrackSelector;\nimport com.google.android.exoplayer2.trackselection.TrackSelection;\nimport com.google.android.exoplayer2.upstream.DataSource;\nimport com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;\nimport com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;\nimport com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;\nimport com.google.android.exoplayer2.upstream.HttpDataSource;\nimport com.google.android.exoplayer2.util.Util;\n\n/**\n * Created by mo on 17-11-29.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class MediaSourceCreator {\n\n    private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();\n\n    private DataSource.Factory mediaDataSourceFactory;\n\n    private Context context;\n    private String userAgent;\n\n    private Handler mainHandler;\n    private EventLogger eventLogger;\n\n    private DefaultTrackSelector trackSelector;\n\n    public MediaSourceCreator(Context context) {\n        this(context, \"exo_video_view\");\n    }\n\n    public MediaSourceCreator(Context context, String userAgent) {\n        this.userAgent = userAgent;\n        this.context = context;\n        mediaDataSourceFactory = buildDataSourceFactory(true);\n        mainHandler = new Handler();\n\n\n        eventLogger = new EventLogger(trackSelector);\n    }\n\n    public EventLogger getEventLogger() {\n        return eventLogger;\n    }\n\n\n    public MediaSource buildMediaSource(Uri uri, String overrideExtension) {\n        @C.ContentType int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)\n                : Util.inferContentType(\".\" + overrideExtension);\n        switch (type) {\n            case C.TYPE_SS:\n                SsMediaSource.Factory factory = new SsMediaSource.Factory(buildDataSourceFactory(false));\n                factory.createMediaSource(uri);\n                return factory.createMediaSource(uri);\n            case C.TYPE_DASH:\n                DashMediaSource.Factory factory1 = new DashMediaSource.Factory(buildDataSourceFactory(false));\n                return factory1.createMediaSource(uri);\n            case C.TYPE_HLS:\n                HlsMediaSource.Factory factory2 = new HlsMediaSource.Factory(buildDataSourceFactory(false));\n                return factory2.createMediaSource(uri);\n            case C.TYPE_OTHER:\n                ProgressiveMediaSource.Factory factory3 = new ProgressiveMediaSource.Factory(buildDataSourceFactory(false));\n                return factory3.createMediaSource(uri);\n            default: {\n                throw new IllegalStateException(\"Unsupported type: \" + type);\n            }\n        }\n    }\n\n\n    private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {\n        return buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);\n    }\n\n    public DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {\n        return new DefaultDataSourceFactory(context, bandwidthMeter,\n                buildHttpDataSourceFactory(bandwidthMeter));\n    }\n\n    public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {\n        return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);\n    }\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/MediaSourceParams.java",
    "content": "package com.jarvanmo.exoplayerview.media;\n\n/**\n * Created by mo on 18-1-11.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class MediaSourceParams {\n    public boolean playWhenReady = true;\n    public long where = 0L;\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/SimpleMediaSource.java",
    "content": "package com.jarvanmo.exoplayerview.media;\n\nimport android.net.Uri;\n\nimport java.util.List;\n\n/**\n * Created by mo on 16-11-30.\n * this package is com.jarvanmo.exoplayerview.ui\n */\n\npublic class SimpleMediaSource implements ExoMediaSource {\n\n    private String displayName;\n\n    private Uri uri;\n\n    private List<Quality> qualities;\n\n    public SimpleMediaSource(String url) {\n        this.uri = Uri.parse(url);\n    }\n\n    public SimpleMediaSource(Uri uri) {\n        this.uri = uri;\n    }\n\n\n    @Override\n    public String name() {\n        return displayName;\n    }\n\n    @Override\n    public List<Quality> qualities() {\n        return qualities;\n    }\n\n    @Override\n    public String extension() {\n        return null;\n    }\n\n    public void setDisplayName(String displayName) {\n        this.displayName = displayName;\n    }\n\n    @Override\n    public Uri uri() {\n        return uri;\n    }\n\n    public void setQualities(List<Quality> qualities) {\n        this.qualities = qualities;\n    }\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/media/SimpleQuality.java",
    "content": "package com.jarvanmo.exoplayerview.media;\n\nimport android.net.Uri;\n\n/**\n * Created by mo on 18-2-7.\n *\n * @author mo\n */\n\npublic class SimpleQuality implements ExoMediaSource.Quality {\n\n    private CharSequence name;\n    private Uri uri;\n    private String quality;\n\n    public SimpleQuality(CharSequence name, Uri uri) {\n        this.name = name;\n        this.uri = uri;\n    }\n\n    @Override\n    public CharSequence getDisplayName() {\n        return name;\n    }\n\n    @Override\n    public Uri getUri() {\n        return uri;\n    }\n\n    @Override\n    public void setUri(Uri uri) {\n        this.uri = uri;\n    }\n\n    @Override\n    public void setDisplayName(CharSequence name) {\n        this.name = name;\n    }\n\n    @Override\n    public void setQuality(String quality) {\n        this.quality = quality;\n    }\n\n    @Override\n    public String getQuality() {\n        return quality;\n    }\n\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/orientation/OnOrientationChangedListener.java",
    "content": "package com.jarvanmo.exoplayerview.orientation;\n\nimport androidx.annotation.IntDef;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * Created by mo on 18-2-5.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic interface OnOrientationChangedListener {\n    int SENSOR_UNKNOWN = -1;\n    int SENSOR_PORTRAIT = SENSOR_UNKNOWN + 1;\n    int SENSOR_LANDSCAPE = SENSOR_PORTRAIT + 1;\n\n    @IntDef({SENSOR_UNKNOWN, SENSOR_PORTRAIT, SENSOR_LANDSCAPE})\n    @Retention(RetentionPolicy.SOURCE)\n    @interface SensorOrientationType {\n\n    }\n\n\n    void onChanged(@SensorOrientationType int orientation);\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/orientation/SensorOrientation.java",
    "content": "package com.jarvanmo.exoplayerview.orientation;\n\nimport android.content.Context;\nimport android.provider.Settings;\nimport android.util.Log;\nimport android.view.OrientationEventListener;\n\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_UNKNOWN;\n\n/**\n * Created by mo on 18-2-5.\n * 剑气纵横三万里 一剑光寒十九洲\n */\n\npublic class SensorOrientation {\n\n    private int oldScreenOrientation = SENSOR_UNKNOWN;\n\n    private final Context context;\n    private final OrientationEventListener screenOrientationEventListener;\n\n    public SensorOrientation(Context context, OnOrientationChangedListener onOrientationChangedListener) {\n        this.context = context;\n        screenOrientationEventListener = new OrientationEventListener(context) {\n            @Override\n            public void onOrientationChanged(int orientation) {\n\n                if (onOrientationChangedListener == null || !isScreenOpenRotate()) {\n                    return;\n                }\n\n\n                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {\n                    onOrientationChangedListener.onChanged(SENSOR_UNKNOWN);\n                    return;  //手机平放时，检测不到有效的角度\n                }\n//只检测是否有四个角度的改变\n                if (orientation > 350 || orientation < 10) { //0度\n                    orientation = 0;\n                } else if (orientation > 80 && orientation < 100) { //90度\n                    orientation = 90;\n                } else if (orientation > 170 && orientation < 190) { //180度\n                    orientation = 180;\n                } else if (orientation > 260 && orientation < 280) { //270度\n                    orientation = 270;\n                } else {\n                    return;\n                }\n\n                if (oldScreenOrientation == orientation) {\n                    onOrientationChangedListener.onChanged(SENSOR_UNKNOWN);\n                    return;\n                }\n\n\n                oldScreenOrientation = orientation;\n\n                if (orientation == 0 || orientation == 180) {\n                    onOrientationChangedListener.onChanged(SENSOR_PORTRAIT);\n                } else {\n                    onOrientationChangedListener.onChanged(SENSOR_LANDSCAPE);\n                }\n            }\n        };\n    }\n\n    private boolean isScreenOpenRotate() {\n\n        int gravity = 0;\n        try {\n\n            gravity = Settings.System.getInt(context.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION);\n\n        } catch (Settings.SettingNotFoundException e) {\n            Log.e(getClass().getSimpleName(), e.getMessage() + \"\");\n        }\n        return 1 == gravity;\n\n    }\n\n    public void enable() {\n        screenOrientationEventListener.enable();\n    }\n\n    public void disable() {\n        screenOrientationEventListener.disable();\n    }\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/ui/ExoVideoPlaybackControlView.java",
    "content": "package com.jarvanmo.exoplayerview.ui;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.os.Build;\nimport android.os.SystemClock;\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.IntDef;\nimport androidx.annotation.Nullable;\nimport androidx.core.content.ContextCompat;\nimport android.text.SpannableString;\nimport android.text.Spanned;\nimport android.text.style.ForegroundColorSpan;\nimport android.util.AttributeSet;\nimport android.util.TypedValue;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.WindowManager;\nimport android.view.animation.AnimationUtils;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport com.google.android.exoplayer2.C;\nimport com.google.android.exoplayer2.ExoPlaybackException;\nimport com.google.android.exoplayer2.ExoPlayer;\nimport com.google.android.exoplayer2.Player;\nimport com.google.android.exoplayer2.Timeline;\nimport com.google.android.exoplayer2.source.hls.HlsManifest;\nimport com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;\nimport com.google.android.exoplayer2.ui.PlayerNotificationManager;\nimport com.google.android.exoplayer2.ui.TimeBar;\nimport com.google.android.exoplayer2.util.Assertions;\nimport com.google.android.exoplayer2.util.RepeatModeUtil;\nimport com.google.android.exoplayer2.util.Util;\nimport com.jarvanmo.exoplayerview.R;\nimport com.jarvanmo.exoplayerview.extension.MultiQualitySelectorAdapter;\nimport com.jarvanmo.exoplayerview.gesture.OnVideoGestureChangeListener;\nimport com.jarvanmo.exoplayerview.gesture.VideoGesture;\nimport com.jarvanmo.exoplayerview.media.ExoMediaSource;\nimport com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener;\nimport com.jarvanmo.exoplayerview.orientation.SensorOrientation;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.util.Arrays;\nimport java.util.Formatter;\nimport java.util.Locale;\n\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_LANDSCAPE;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_PORTRAIT;\nimport static com.jarvanmo.exoplayerview.orientation.OnOrientationChangedListener.SENSOR_UNKNOWN;\n\n/**\n * Created by mo on 16-11-7.\n *\n * @author mo\n *         <p>\n *         * <h3>Specifying a custom layout file</h3>\n *         Defining your own {@code exo_video_playback_control_view.xml} is useful to customize the layout of\n *         ExoVideoPlaybackControlView throughout your application. It's also possible to customize the layout for a\n *         single instance in a layout file. This is achieved by setting the {@code controller_layout_id}\n *         attribute on a ExoVideoPlaybackControlView. This will cause the specified layout to be inflated instead\n *         of {@code exo_video_playback_control_view.xml} for only the instance on which the attribute is set.\n */\n\npublic class ExoVideoPlaybackControlView extends FrameLayout {\n\n    /**\n     * to get  {@link ExoVideoView}\n     */\n    public interface VideoViewAccessor {\n\n        View attachVideoView();\n    }\n\n\n    /**\n     * to get  {@link Player}\n     */\n    public interface PlayerAccessor {\n\n        Player attachPlayer();\n    }\n\n\n    /**\n     * Listener to be notified about changes of the visibility of the UI control.\n     */\n    public interface VisibilityListener {\n\n        /**\n         * Called when the visibility changes.\n         *\n         * @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}.\n         */\n        void onVisibilityChange(int visibility);\n\n    }\n\n\n    public interface ExoClickListener {\n\n        /***\n         * called when buttons clicked in controller\n         * @param view  null when back pressed.\n         * @param isPortrait the controller is portrait  or not\n         * @return will interrupt operation in controller if return true\n         * **/\n        boolean onClick(@Nullable View view, boolean isPortrait);\n\n    }\n\n\n    public interface OrientationListener {\n        void onOrientationChanged(@OnOrientationChangedListener.SensorOrientationType int orientation);\n    }\n\n\n    /**\n     * The default fast forward increment, in milliseconds.\n     */\n    public static final int DEFAULT_FAST_FORWARD_MS = 15000;\n    /**\n     * The default rewind increment, in milliseconds.\n     */\n    public static final int DEFAULT_REWIND_MS = 5000;\n    /**\n     * The default show timeout, in milliseconds.\n     */\n    public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000;\n    /**\n     * The default repeat toggle modes.\n     */\n    public static final @RepeatModeUtil.RepeatToggleModes\n    int DEFAULT_REPEAT_TOGGLE_MODES =\n            RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE;\n\n    /**\n     * The maximum number of windows that can be shown in a multi-window time bar.\n     */\n    public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100;\n\n    private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;\n\n\n    //        <!--<enum getDisplayName=\"all\" value=\"0b1111\"/>-->\n//    <!--<enum getDisplayName=\"top\" value=\"0b1000\"/>-->\n//    <!--<enum getDisplayName=\"top_landscape\" value=\"0b0100\"/>-->\n//    <!--<enum getDisplayName=\"bottom\" value=\"0b0010\"/>-->\n//    <!--<enum getDisplayName=\"bottom_landscape\" value=\"0b0001\"/>-->\n    public static final int CONTROLLER_MODE_NONE = 0b0000;\n    public static final int CONTROLLER_MODE_ALL = 0b1111;\n    public static final int CONTROLLER_MODE_TOP = 0b1000;\n    public static final int CONTROLLER_MODE_TOP_LANDSCAPE = 0b0100;\n    public static final int CONTROLLER_MODE_BOTTOM = 0b0010;\n    public static final int CONTROLLER_MODE_BOTTOM_LANDSCAPE = 0b0001;\n\n    @IntDef({CONTROLLER_MODE_NONE,\n            CONTROLLER_MODE_ALL,\n            CONTROLLER_MODE_TOP, CONTROLLER_MODE_TOP_LANDSCAPE, CONTROLLER_MODE_BOTTOM, CONTROLLER_MODE_BOTTOM_LANDSCAPE})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface ControllerModeType {\n\n    }\n\n    public static final int CUSTOM_VIEW_TOP = 1;\n    public static final int CUSTOM_VIEW_TOP_LANDSCAPE = CUSTOM_VIEW_TOP + 1;\n    public static final int CUSTOM_VIEW_BOTTOM_LANDSCAPE = CUSTOM_VIEW_TOP_LANDSCAPE + 1;\n\n    @IntDef({CUSTOM_VIEW_TOP, CUSTOM_VIEW_TOP_LANDSCAPE, CUSTOM_VIEW_BOTTOM_LANDSCAPE})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface CustomViewType {\n\n    }\n\n    private final ComponentListener componentListener;\n    private final View previousButton;\n    private final View nextButton;\n    private final View playButton;\n    private final View pauseButton;\n    private final View fastForwardButton;\n    private final View rewindButton;\n    private final ImageView repeatToggleButton;\n    private final View shuffleButton;\n    private final TextView durationView;\n    private final TextView positionView;\n    private final TimeBar timeBar;\n    private final StringBuilder formatBuilder;\n    private final Formatter formatter;\n    private final Timeline.Period period;\n    private final Timeline.Window window;\n\n\n    private final Drawable repeatOffButtonDrawable;\n    private final Drawable repeatOneButtonDrawable;\n    private final Drawable repeatAllButtonDrawable;\n    private final String repeatOffButtonContentDescription;\n    private final String repeatOneButtonContentDescription;\n    private final String repeatAllButtonContentDescription;\n\n    private Player player;\n    private com.google.android.exoplayer2.ControlDispatcher controlDispatcher;\n    private VisibilityListener visibilityListener;\n\n    private boolean isAttachedToWindow;\n    private boolean showMultiWindowTimeBar;\n    private boolean multiWindowTimeBar;\n    private boolean scrubbing;\n    private int rewindMs;\n    private int fastForwardMs;\n    private int showTimeoutMs;\n    private @RepeatModeUtil.RepeatToggleModes\n    int repeatToggleModes;\n    private boolean showShuffleButton;\n    private long hideAtMs;\n    private long[] adGroupTimesMs;\n    private boolean[] playedAdGroups;\n    private long[] extraAdGroupTimesMs;\n    private boolean[] extraPlayedAdGroups;\n\n    private final Runnable updateProgressAction = this::updateProgress;\n\n    private final Runnable hideAction = this::hide;\n\n\n    private final TimeBar timeBarLandscape;\n    private final View playButtonLandScape;\n    private final View pauseButtonLandScape;\n    private final TextView durationViewLandscape;\n    private final View enterFullscreen;\n    private final View exitFullscreen;\n\n\n    private final View exoPlayerControllerTop;\n    private final View exoPlayerControllerTopLandscape;\n    private final View exoPlayerControllerBottom;\n    private final View exoPlayerControllerBottomLandscape;\n\n    private final View centerInfoWrapper;\n    private final TextView centerInfo;\n\n    private final TextView exoPlayerVideoName;\n    private final TextView exoPlayerVideoNameLandscape;\n\n    private final TextView exoPlayerCurrentQualityLandscape;\n\n    private final ViewGroup topCustomView;\n    private final ViewGroup topCustomViewLandscape;\n    private final ViewGroup bottomCustomViewLandscape;\n\n\n    private final TextView centerError;\n    private final ProgressBar loadingBar;\n\n\n    private final View back;\n    private final View backLandscape;\n\n    private boolean portrait = true;\n\n\n    private SensorOrientation sensorOrientation;\n\n    private OrientationListener orientationListener;\n    private ExoClickListener backListener;\n\n\n    private boolean isHls;\n\n    private int displayMode = CONTROLLER_MODE_ALL;\n\n    private MultiQualitySelectorAdapter.VisibilityCallback qualityVisibilityCallback;\n\n    private VideoViewAccessor videoViewAccessor;\n    private  VideoGesture videoGesture;\n\n    public ExoVideoPlaybackControlView(Context context) {\n        this(context, null);\n    }\n\n    public ExoVideoPlaybackControlView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ExoVideoPlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, attrs);\n    }\n\n    public ExoVideoPlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr,\n                                       AttributeSet playbackAttrs) {\n        super(context, attrs, defStyleAttr);\n\n\n        int controllerLayoutId = R.layout.exo_video_playback_control_view;\n        rewindMs = DEFAULT_REWIND_MS;\n        fastForwardMs = DEFAULT_FAST_FORWARD_MS;\n        showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;\n        repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;\n        showShuffleButton = false;\n        boolean enableGesture = true;\n\n        int controllerBackgroundId = 0;\n\n        if (playbackAttrs != null) {\n            TypedArray a = context.getTheme().obtainStyledAttributes(playbackAttrs,\n                    R.styleable.ExoVideoPlaybackControlView, 0, 0);\n            try {\n                rewindMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_rewind_increment, rewindMs);\n                fastForwardMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_fastforward_increment,\n                        fastForwardMs);\n                showTimeoutMs = a.getInt(R.styleable.ExoVideoPlaybackControlView_show_timeout, showTimeoutMs);\n                controllerLayoutId = a.getResourceId(R.styleable.ExoVideoPlaybackControlView_controller_layout_id,\n                        controllerLayoutId);\n                repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);\n                showShuffleButton = a.getBoolean(R.styleable.ExoVideoPlaybackControlView_show_shuffle_button,\n                        showShuffleButton);\n                displayMode = a.getInt(R.styleable.ExoVideoPlaybackControlView_controller_display_mode, CONTROLLER_MODE_ALL);\n\n                controllerBackgroundId = a.getResourceId(R.styleable.ExoVideoPlaybackControlView_controller_background, 0);\n                enableGesture = a.getBoolean(R.styleable.ExoVideoPlaybackControlView_enableGesture,enableGesture);\n            } finally {\n                a.recycle();\n            }\n        }\n\n\n        period = new Timeline.Period();\n        window = new Timeline.Window();\n        formatBuilder = new StringBuilder();\n        formatter = new Formatter(formatBuilder, Locale.getDefault());\n        adGroupTimesMs = new long[0];\n        playedAdGroups = new boolean[0];\n        extraAdGroupTimesMs = new long[0];\n        extraPlayedAdGroups = new boolean[0];\n        componentListener = new ComponentListener();\n        controlDispatcher = new com.google.android.exoplayer2.DefaultControlDispatcher();\n\n        LayoutInflater.from(context).inflate(controllerLayoutId, this);\n        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);\n\n        durationView = findViewById(R.id.exo_player_duration);\n        positionView = findViewById(R.id.exo_player_position);\n        timeBar = findViewById(R.id.exo_player_progress);\n        if (timeBar != null) {\n            timeBar.addListener(componentListener);\n        }\n\n\n        playButton = findViewById(R.id.exo_player_play);\n        if (playButton != null) {\n            playButton.setOnClickListener(componentListener);\n        }\n\n\n        pauseButton = findViewById(R.id.exo_player_pause);\n        if (pauseButton != null) {\n            pauseButton.setOnClickListener(componentListener);\n        }\n\n\n        previousButton = findViewById(R.id.exo_prev);\n        if (previousButton != null) {\n            previousButton.setOnClickListener(componentListener);\n        }\n        nextButton = findViewById(R.id.exo_next);\n        if (nextButton != null) {\n            nextButton.setOnClickListener(componentListener);\n        }\n        rewindButton = findViewById(R.id.exo_rew);\n        if (rewindButton != null) {\n            rewindButton.setOnClickListener(componentListener);\n        }\n        fastForwardButton = findViewById(R.id.exo_ffwd);\n        if (fastForwardButton != null) {\n            fastForwardButton.setOnClickListener(componentListener);\n        }\n        repeatToggleButton = findViewById(R.id.exo_repeat_toggle);\n        if (repeatToggleButton != null) {\n            repeatToggleButton.setOnClickListener(componentListener);\n        }\n        shuffleButton = findViewById(R.id.exo_shuffle);\n        if (shuffleButton != null) {\n            shuffleButton.setOnClickListener(componentListener);\n        }\n        Resources resources = context.getResources();\n        repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_off);\n        repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_one);\n        repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_all);\n        repeatOffButtonContentDescription = resources.getString(\n                R.string.exo_controls_repeat_off_description);\n        repeatOneButtonContentDescription = resources.getString(\n                R.string.exo_controls_repeat_one_description);\n        repeatAllButtonContentDescription = resources.getString(\n                R.string.exo_controls_repeat_all_description);\n\n\n        durationViewLandscape = findViewById(R.id.exo_player_position_duration_landscape);\n\n        timeBarLandscape = findViewById(R.id.exo_player_progress_landscape);\n        if (timeBarLandscape != null) {\n            timeBarLandscape.addListener(componentListener);\n        }\n\n        playButtonLandScape = findViewById(R.id.exo_player_play_landscape);\n        if (playButtonLandScape != null) {\n            playButtonLandScape.setOnClickListener(componentListener);\n        }\n\n        pauseButtonLandScape = findViewById(R.id.exo_player_pause_landscape);\n        if (pauseButtonLandScape != null) {\n            pauseButtonLandScape.setOnClickListener(componentListener);\n        }\n\n        enterFullscreen = findViewById(R.id.exo_player_enter_fullscreen);\n        if (enterFullscreen != null) {\n            enterFullscreen.setOnClickListener(componentListener);\n        }\n\n\n        exitFullscreen = findViewById(R.id.exo_player_exit_fullscreen);\n        if (exitFullscreen != null) {\n            exitFullscreen.setOnClickListener(componentListener);\n        }\n\n        centerInfoWrapper = findViewById(R.id.exo_player_center_info_wrapper);\n        centerInfo = findViewById(R.id.exo_player_center_text);\n\n\n        exoPlayerControllerTop = findViewById(R.id.exo_player_controller_top);\n        if (exoPlayerControllerTop != null && controllerBackgroundId != 0) {\n            exoPlayerControllerTop.setBackgroundResource(controllerBackgroundId);\n        }\n\n        exoPlayerControllerTopLandscape = findViewById(R.id.exo_player_controller_top_landscape);\n        if (exoPlayerControllerTopLandscape != null && controllerBackgroundId != 0) {\n            exoPlayerControllerTopLandscape.setBackgroundResource(controllerBackgroundId);\n        }\n\n        exoPlayerControllerBottom = findViewById(R.id.exo_player_controller_bottom);\n        if (exoPlayerControllerBottom != null && controllerBackgroundId != 0) {\n            exoPlayerControllerBottom.setBackgroundResource(controllerBackgroundId);\n        }\n\n        exoPlayerControllerBottomLandscape = findViewById(R.id.exo_player_controller_bottom_landscape);\n        if (exoPlayerControllerBottomLandscape != null && controllerBackgroundId != 0) {\n            exoPlayerControllerBottomLandscape.setBackgroundResource(controllerBackgroundId);\n        }\n\n\n        exoPlayerVideoName = findViewById(R.id.exo_player_video_name);\n        if (exoPlayerVideoName != null) {\n            exoPlayerVideoName.setOnClickListener(componentListener);\n        }\n\n        exoPlayerVideoNameLandscape = findViewById(R.id.exo_player_video_name_landscape);\n        if (exoPlayerVideoNameLandscape != null) {\n            exoPlayerVideoNameLandscape.setOnClickListener(componentListener);\n        }\n\n        back = findViewById(R.id.exo_player_controller_back);\n        if (back != null) {\n            back.setOnClickListener(componentListener);\n        }\n\n        backLandscape = findViewById(R.id.exo_player_controller_back_landscape);\n        if(backLandscape != null){\n            backLandscape.setOnClickListener(componentListener);\n        }\n\n        if (centerInfoWrapper != null) {\n            setupVideoGesture(enableGesture);\n        }\n\n        exoPlayerCurrentQualityLandscape = findViewById(R.id.exo_player_current_quality_landscape);\n        if (exoPlayerCurrentQualityLandscape != null) {\n            exoPlayerCurrentQualityLandscape.setOnClickListener(componentListener);\n        }\n\n\n        topCustomView = findViewById(R.id.exo_player_controller_top_custom_view);\n        topCustomViewLandscape = findViewById(R.id.exo_player_controller_top_custom_view_landscape);\n        bottomCustomViewLandscape = findViewById(R.id.exo_player_controller_bottom_custom_view_landscape);\n\n        centerError = findViewById(R.id.exo_player_center_error);\n        loadingBar = findViewById(R.id.exo_player_loading);\n        sensorOrientation = new SensorOrientation(getContext(), this::changeOrientation);\n        showControllerByDisplayMode();\n\n        showUtilHideCalled();\n    }\n\n\n    private void setupVideoGesture(boolean enableGesture) {\n        OnVideoGestureChangeListener onVideoGestureChangeListener = new OnVideoGestureChangeListener() {\n\n            @Override\n            public void onVolumeChanged(int range, int type) {\n                show();\n                int drawableId;\n                if (type == VOLUME_CHANGED_MUTE) {\n                    drawableId = R.drawable.ic_volume_mute_white_36dp;\n                } else if (type == VOLUME_CHANGED_INCREMENT) {\n                    drawableId = R.drawable.ic_volume_up_white_36dp;\n                } else {\n                    drawableId = R.drawable.ic_volume_down_white_36dp;\n                }\n\n                setVolumeOrBrightnessInfo(getContext().getString(R.string.volume_changing, range), drawableId);\n            }\n\n            @Override\n            public void onBrightnessChanged(int brightnessPercent) {\n                show();\n                String info = getContext().getString(R.string.brightness_changing, brightnessPercent);\n                int drawable = whichBrightnessImageToUse(brightnessPercent);\n                setVolumeOrBrightnessInfo(info, drawable);\n            }\n\n            @Override\n            public void onNoGesture() {\n\n                if (centerInfo == null) {\n                    return;\n                }\n                centerInfo.startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));\n                centerInfo.setVisibility(GONE);\n            }\n\n            @Override\n            public void onShowSeekSize(long seekSize, boolean fastForward) {\n                if (isHls) {\n                    return;\n                }\n\n                show();\n                seekTo(seekSize);\n                if (centerInfo == null) {\n                    return;\n                }\n\n                if (centerError != null && centerError.getVisibility() == VISIBLE) {\n                    centerError.setVisibility(GONE);\n                }\n                centerInfo.setVisibility(VISIBLE);\n                centerInfo.setText(generateFastForwardOrRewindTxt(seekSize));\n                int drawableId = fastForward ? R.drawable.ic_fast_forward_white_36dp : R.drawable.ic_fast_rewind_white_36dp;\n                Drawable drawable = ContextCompat.getDrawable(getContext(), drawableId);\n                centerInfo.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);\n            }\n        };\n\n\n        videoGesture = new VideoGesture(getContext(), onVideoGestureChangeListener, () -> player);\n        if(!enableGesture){\n            videoGesture.disable();\n        }\n        centerInfoWrapper.setOnClickListener(componentListener);\n        centerInfoWrapper.setOnTouchListener(videoGesture);\n\n    }\n\n\n    private CharSequence generateFastForwardOrRewindTxt(long changingTime) {\n\n        long duration = player == null ? 0 : player.getDuration();\n        String result = Util.getStringForTime(formatBuilder, formatter, changingTime);\n        result = result + \"/\";\n        result = result + Util.getStringForTime(formatBuilder, formatter, duration);\n\n        int index = result.indexOf(\"/\");\n\n        SpannableString spannableString = new SpannableString(result);\n\n\n        TypedValue typedValue = new TypedValue();\n        TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorAccent});\n        int color = a.getColor(0, 0);\n        a.recycle();\n        spannableString.setSpan(new ForegroundColorSpan(color), 0, index, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        spannableString.setSpan(new ForegroundColorSpan(Color.WHITE), index, result.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);\n        return spannableString;\n    }\n\n    @DrawableRes\n    private int whichBrightnessImageToUse(int brightnessInt) {\n        if (brightnessInt <= 15) {\n            return R.drawable.ic_brightness_1_white_36dp;\n        } else if (brightnessInt <= 30) {\n            return R.drawable.ic_brightness_2_white_36dp;\n        } else if (brightnessInt <= 45) {\n            return R.drawable.ic_brightness_3_white_36dp;\n        } else if (brightnessInt <= 60) {\n            return R.drawable.ic_brightness_4_white_36dp;\n        } else if (brightnessInt <= 75) {\n            return R.drawable.ic_brightness_5_white_36dp;\n        } else if (brightnessInt <= 90) {\n            return R.drawable.ic_brightness_6_white_36dp;\n        } else {\n            return R.drawable.ic_brightness_7_white_36dp;\n        }\n\n    }\n\n    private void setVolumeOrBrightnessInfo(String txt, @DrawableRes int drawableId) {\n        if (centerInfo == null) {\n            return;\n        }\n\n        if (centerError != null && centerError.getVisibility() == VISIBLE) {\n            centerError.setVisibility(GONE);\n        }\n\n        centerInfo.setVisibility(VISIBLE);\n        centerInfo.setText(txt);\n        centerInfo.setTextColor(ContextCompat.getColor(getContext(), android.R.color.white));\n        centerInfo.setCompoundDrawablesWithIntrinsicBounds(null, ContextCompat.getDrawable(getContext(), drawableId), null, null);\n    }\n\n\n    @SuppressWarnings(\"ResourceType\")\n    private static @RepeatModeUtil.RepeatToggleModes\n    int getRepeatToggleModes(TypedArray a,\n                             @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {\n        return a.getInt(R.styleable.ExoVideoPlaybackControlView_repeat_toggle_modes, repeatToggleModes);\n    }\n\n    /**\n     * Returns the {@link Player} currently being controlled by this view, or null if no player is\n     * set.\n     */\n    public Player getPlayer() {\n        return player;\n    }\n\n    /**\n     * Sets the {@link Player} to control.\n     *\n     * @param player The {@link Player} to control.\n     */\n    public void setPlayer(Player player) {\n        if (this.player == player) {\n            return;\n        }\n        if (this.player != null) {\n            this.player.removeListener(componentListener);\n        }\n        this.player = player;\n        if (player != null) {\n            player.addListener(componentListener);\n        }\n        updateAll();\n    }\n\n    /**\n     * Sets whether the time bar should show all windows, as opposed to just the current one. If the\n     * timeline has a period with unknown duration or more than\n     * {@link #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will fall back to showing a\n     * single window.\n     *\n     * @param showMultiWindowTimeBar Whether the time bar should show all windows.\n     */\n    public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {\n        this.showMultiWindowTimeBar = showMultiWindowTimeBar;\n        updateTimeBarMode();\n    }\n\n    /**\n     * Sets the millisecond positions of extra ad markers relative to the start of the window (or\n     * timeline, if in multi-window mode) and whether each extra ad has been played or not. The\n     * markers are shown in addition to any ad markers for ads in the player's timeline.\n     *\n     * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or\n     *                            {@code null} to show no extra ad markers.\n     * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad\n     *                            markers.\n     */\n    public void setExtraAdGroupMarkers(@Nullable long[] extraAdGroupTimesMs,\n                                       @Nullable boolean[] extraPlayedAdGroups) {\n        if (extraAdGroupTimesMs == null) {\n            this.extraAdGroupTimesMs = new long[0];\n            this.extraPlayedAdGroups = new boolean[0];\n        } else {\n            Assertions.checkArgument(extraAdGroupTimesMs.length == extraPlayedAdGroups.length);\n            this.extraAdGroupTimesMs = extraAdGroupTimesMs;\n            this.extraPlayedAdGroups = extraPlayedAdGroups;\n        }\n        updateProgress();\n    }\n\n    /**\n     * Sets the {@link VisibilityListener}.\n     *\n     * @param listener The listener to be notified about visibility changes.\n     */\n    public void setVisibilityListener(VisibilityListener listener) {\n        this.visibilityListener = listener;\n    }\n\n    /**\n     * Sets the {@link com.google.android.exoplayer2.ControlDispatcher}.\n     *\n     * @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}, or null\n     *                          to use {@link com.google.android.exoplayer2.DefaultControlDispatcher}.\n     */\n    public void setControlDispatcher(\n            @Nullable com.google.android.exoplayer2.ControlDispatcher controlDispatcher) {\n        this.controlDispatcher = controlDispatcher == null\n                ? new com.google.android.exoplayer2.DefaultControlDispatcher() : controlDispatcher;\n    }\n\n    /**\n     * Sets the rewind increment in milliseconds.\n     *\n     * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the\n     *                 rewind button to be disabled.\n     */\n    public void setRewindIncrementMs(int rewindMs) {\n        this.rewindMs = rewindMs;\n        updateNavigation();\n    }\n\n    /**\n     * Sets the fast forward increment in milliseconds.\n     *\n     * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will\n     *                      cause the fast forward button to be disabled.\n     */\n    public void setFastForwardIncrementMs(int fastForwardMs) {\n        this.fastForwardMs = fastForwardMs;\n        updateNavigation();\n    }\n\n    /**\n     * Returns the playback controls timeout. The playback controls are automatically hidden after\n     * this duration of time has elapsed without user input.\n     *\n     * @return The duration in milliseconds. A non-positive value indicates that the controls will\n     * remain visible indefinitely.\n     */\n    public int getShowTimeoutMs() {\n        return showTimeoutMs;\n    }\n\n    /**\n     * Sets the playback controls timeout. The playback controls are automatically hidden after this\n     * duration of time has elapsed without user input.\n     *\n     * @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls\n     *                      to remain visible indefinitely.\n     */\n    public void setShowTimeoutMs(int showTimeoutMs) {\n        this.showTimeoutMs = showTimeoutMs;\n    }\n\n    /**\n     * Returns which repeat toggle modes are enabled.\n     *\n     * @return The currently enabled {@link RepeatModeUtil.RepeatToggleModes}.\n     */\n    public @RepeatModeUtil.RepeatToggleModes\n    int getRepeatToggleModes() {\n        return repeatToggleModes;\n    }\n\n    /**\n     * Sets which repeat toggle modes are enabled.\n     *\n     * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.\n     */\n    public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {\n        this.repeatToggleModes = repeatToggleModes;\n        if (player != null) {\n            @Player.RepeatMode int currentMode = player.getRepeatMode();\n            if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE\n                    && currentMode != Player.REPEAT_MODE_OFF) {\n                controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_OFF);\n            } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE\n                    && currentMode == Player.REPEAT_MODE_ALL) {\n                controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ONE);\n            } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL\n                    && currentMode == Player.REPEAT_MODE_ONE) {\n                controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ALL);\n            }\n        }\n    }\n\n    /**\n     * Returns whether the shuffle button is shown.\n     */\n    public boolean getShowShuffleButton() {\n        return showShuffleButton;\n    }\n\n    /**\n     * Sets whether the shuffle button is shown.\n     *\n     * @param showShuffleButton Whether the shuffle button is shown.\n     */\n    public void setShowShuffleButton(boolean showShuffleButton) {\n        this.showShuffleButton = showShuffleButton;\n        updateShuffleButton();\n    }\n\n    /**\n     * Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will\n     * be automatically hidden after this duration of time has elapsed without user input.\n     */\n\n    public void show() {\n\n        if (!isVisible()) {\n            setVisibility(VISIBLE);\n            if (visibilityListener != null) {\n                visibilityListener.onVisibilityChange(getVisibility());\n            }\n\n            if (portrait) {\n                changeSystemUiVisibilityPortrait();\n            }\n\n            updateAll();\n            requestPlayPauseFocus();\n        }\n        // Call hideAfterTimeout even if already visible to reset the timeout.\n        hideAfterTimeout();\n\n    }\n\n    /**\n     * Hides the controller.\n     */\n    public void hide() {\n        if (isVisible()) {\n            setVisibility(GONE);\n            if (visibilityListener != null) {\n                visibilityListener.onVisibilityChange(getVisibility());\n            }\n            removeCallbacks(updateProgressAction);\n            removeCallbacks(hideAction);\n            hideAtMs = C.TIME_UNSET;\n\n            if (!portrait) {\n                changeSystemUiVisibilityLandscape();\n            }\n        }\n    }\n\n    /**\n     * Returns whether the controller is currently visible.\n     */\n    public boolean isVisible() {\n        return getVisibility() == VISIBLE;\n    }\n\n    private void hideAfterTimeout() {\n        removeCallbacks(hideAction);\n        if (showTimeoutMs > 0) {\n            hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs;\n            if (isAttachedToWindow) {\n                postDelayed(hideAction, showTimeoutMs);\n            }\n        } else {\n            hideAtMs = C.TIME_UNSET;\n        }\n    }\n\n    private void updateAll() {\n        updatePlayPauseButton();\n        updateNavigation();\n        updateRepeatModeButton();\n        updateShuffleButton();\n        updateProgress();\n    }\n\n    private void updateNavigation() {\n        if (!isVisible() || !isAttachedToWindow) {\n            return;\n        }\n        Timeline timeline = player != null ? player.getCurrentTimeline() : null;\n        boolean haveNonEmptyTimeline = timeline != null && !timeline.isEmpty();\n        boolean isSeekable = false;\n        boolean enablePrevious = false;\n        boolean enableNext = false;\n        if (haveNonEmptyTimeline && !player.isPlayingAd()) {\n            int windowIndex = player.getCurrentWindowIndex();\n            timeline.getWindow(windowIndex, window);\n            isSeekable = window.isSeekable;\n            enablePrevious = isSeekable || !window.isDynamic\n                    || player.getPreviousWindowIndex() != C.INDEX_UNSET;\n            enableNext = window.isDynamic || player.getNextWindowIndex() != C.INDEX_UNSET;\n        }\n        setButtonEnabled(enablePrevious, previousButton);\n        setButtonEnabled(enableNext, nextButton);\n        setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton);\n        setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton);\n        if (timeBar != null) {\n            timeBar.setEnabled(isSeekable && !isHls);\n        }\n        if (timeBarLandscape != null) {\n            timeBarLandscape.setEnabled(isSeekable && !isHls);\n        }\n    }\n\n    private void updatePlayPauseButton() {\n        if (!isVisible() || !isAttachedToWindow) {\n            return;\n        }\n        boolean requestPlayPauseFocus = false;\n        boolean playing = player != null && player.getPlayWhenReady();\n        if (playButton != null) {\n            requestPlayPauseFocus |= playing && playButton.isFocused();\n            playButton.setVisibility(playing ? View.GONE : View.VISIBLE);\n        }\n        if (pauseButton != null) {\n            requestPlayPauseFocus |= !playing && pauseButton.isFocused();\n            pauseButton.setVisibility(!playing ? View.GONE : View.VISIBLE);\n        }\n\n\n        if (playButtonLandScape != null) {\n            requestPlayPauseFocus |= playing && playButtonLandScape.isFocused();\n            playButtonLandScape.setVisibility(playing ? View.GONE : View.VISIBLE);\n        }\n        if (pauseButtonLandScape != null) {\n            requestPlayPauseFocus |= !playing && pauseButtonLandScape.isFocused();\n            pauseButtonLandScape.setVisibility(!playing ? View.GONE : View.VISIBLE);\n        }\n\n\n        if (requestPlayPauseFocus) {\n            requestPlayPauseFocus();\n        }\n    }\n\n    private void updateRepeatModeButton() {\n        if (!isVisible() || !isAttachedToWindow || repeatToggleButton == null) {\n            return;\n        }\n        if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {\n            repeatToggleButton.setVisibility(View.GONE);\n            return;\n        }\n        if (player == null) {\n            setButtonEnabled(false, repeatToggleButton);\n            return;\n        }\n        setButtonEnabled(true, repeatToggleButton);\n        switch (player.getRepeatMode()) {\n            case Player.REPEAT_MODE_OFF:\n                repeatToggleButton.setImageDrawable(repeatOffButtonDrawable);\n                repeatToggleButton.setContentDescription(repeatOffButtonContentDescription);\n                break;\n            case Player.REPEAT_MODE_ONE:\n                repeatToggleButton.setImageDrawable(repeatOneButtonDrawable);\n                repeatToggleButton.setContentDescription(repeatOneButtonContentDescription);\n                break;\n            case Player.REPEAT_MODE_ALL:\n                repeatToggleButton.setImageDrawable(repeatAllButtonDrawable);\n                repeatToggleButton.setContentDescription(repeatAllButtonContentDescription);\n                break;\n        }\n        repeatToggleButton.setVisibility(View.VISIBLE);\n    }\n\n    private void updateShuffleButton() {\n        if (!isVisible() || !isAttachedToWindow || shuffleButton == null) {\n            return;\n        }\n        if (!showShuffleButton) {\n            shuffleButton.setVisibility(View.GONE);\n        } else if (player == null) {\n            setButtonEnabled(false, shuffleButton);\n        } else {\n            shuffleButton.setAlpha(player.getShuffleModeEnabled() ? 1f : 0.3f);\n            shuffleButton.setEnabled(true);\n            shuffleButton.setVisibility(View.VISIBLE);\n        }\n    }\n\n    private void updateTimeBarMode() {\n        if (player == null) {\n            return;\n        }\n        multiWindowTimeBar = showMultiWindowTimeBar\n                && canShowMultiWindowTimeBar(player.getCurrentTimeline(), window);\n    }\n\n    private void updateProgress() {\n        if (!isVisible() || !isAttachedToWindow) {\n            return;\n        }\n\n        long position = 0;\n        long bufferedPosition = 0;\n        long duration = 0;\n        if (player != null) {\n            long currentWindowTimeBarOffsetUs = 0;\n            long durationUs = 0;\n            int adGroupCount = 0;\n            Timeline timeline = player.getCurrentTimeline();\n            if (!timeline.isEmpty()) {\n                int currentWindowIndex = player.getCurrentWindowIndex();\n                int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex;\n                int lastWindowIndex =\n                        multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex;\n                for (int i = firstWindowIndex; i <= lastWindowIndex; i++) {\n                    if (i == currentWindowIndex) {\n                        currentWindowTimeBarOffsetUs = durationUs;\n                    }\n                    timeline.getWindow(i, window);\n                    if (window.durationUs == C.TIME_UNSET) {\n                        Assertions.checkState(!multiWindowTimeBar);\n                        break;\n                    }\n                    for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) {\n                        timeline.getPeriod(j, period);\n                        int periodAdGroupCount = period.getAdGroupCount();\n                        for (int adGroupIndex = 0; adGroupIndex < periodAdGroupCount; adGroupIndex++) {\n                            long adGroupTimeInPeriodUs = period.getAdGroupTimeUs(adGroupIndex);\n                            if (adGroupTimeInPeriodUs == C.TIME_END_OF_SOURCE) {\n                                if (period.durationUs == C.TIME_UNSET) {\n                                    // Don't show ad markers for postrolls in periods with unknown duration.\n                                    continue;\n                                }\n                                adGroupTimeInPeriodUs = period.durationUs;\n                            }\n                            long adGroupTimeInWindowUs = adGroupTimeInPeriodUs + period.getPositionInWindowUs();\n                            if (adGroupTimeInWindowUs >= 0 && adGroupTimeInWindowUs <= window.durationUs) {\n                                if (adGroupCount == adGroupTimesMs.length) {\n                                    int newLength = adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2;\n                                    adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength);\n                                    playedAdGroups = Arrays.copyOf(playedAdGroups, newLength);\n                                }\n                                adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs);\n                                playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex);\n                                adGroupCount++;\n                            }\n                        }\n                    }\n                    durationUs += window.durationUs;\n                }\n            }\n            duration = C.usToMs(durationUs);\n            position = C.usToMs(currentWindowTimeBarOffsetUs);\n            bufferedPosition = position;\n            if (player.isPlayingAd()) {\n                position += player.getContentPosition();\n                bufferedPosition = position;\n            } else {\n                position += player.getCurrentPosition();\n                bufferedPosition += player.getBufferedPosition();\n            }\n            if (timeBar != null) {\n                int extraAdGroupCount = extraAdGroupTimesMs.length;\n                int totalAdGroupCount = adGroupCount + extraAdGroupCount;\n                if (totalAdGroupCount > adGroupTimesMs.length) {\n                    adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount);\n                    playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount);\n                }\n                System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount);\n                System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount);\n                timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount);\n            }\n        }\n        if (durationView != null && !isHls) {\n            durationView.setText(Util.getStringForTime(formatBuilder, formatter, duration));\n        }\n\n        if (durationViewLandscape != null && !isHls) {\n            String positionStr = Util.getStringForTime(formatBuilder, formatter, position);\n            String durationStr = Util.getStringForTime(formatBuilder, formatter, duration);\n            durationViewLandscape.setText(positionStr.concat(\"/\").concat(durationStr));\n        }\n\n        if (positionView != null && !scrubbing && !isHls) {\n            positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));\n        }\n\n\n        if (timeBar != null && !isHls) {\n            timeBar.setPosition(position);\n            timeBar.setBufferedPosition(bufferedPosition);\n            timeBar.setDuration(duration);\n        }\n\n        if (timeBarLandscape != null && !isHls) {\n            timeBarLandscape.setPosition(position);\n            timeBarLandscape.setBufferedPosition(bufferedPosition);\n            timeBarLandscape.setDuration(duration);\n        }\n\n\n        // Cancel any pending updates and schedule a new one if necessary.\n        removeCallbacks(updateProgressAction);\n        int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState();\n        if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) {\n            long delayMs;\n            if (player.getPlayWhenReady() && playbackState == Player.STATE_READY) {\n                float playbackSpeed = player.getPlaybackParameters().speed;\n                if (playbackSpeed <= 0.1f) {\n                    delayMs = 1000;\n                } else if (playbackSpeed <= 5f) {\n                    long mediaTimeUpdatePeriodMs = 1000 / Math.max(1, Math.round(1 / playbackSpeed));\n                    long mediaTimeDelayMs = mediaTimeUpdatePeriodMs - (position % mediaTimeUpdatePeriodMs);\n                    if (mediaTimeDelayMs < (mediaTimeUpdatePeriodMs / 5)) {\n                        mediaTimeDelayMs += mediaTimeUpdatePeriodMs;\n                    }\n                    delayMs = playbackSpeed == 1 ? mediaTimeDelayMs\n                            : (long) (mediaTimeDelayMs / playbackSpeed);\n                } else {\n                    delayMs = 200;\n                }\n            } else {\n                delayMs = 1000;\n            }\n            postDelayed(updateProgressAction, delayMs);\n        }\n    }\n\n    private void requestPlayPauseFocus() {\n        boolean playing = player != null && player.getPlayWhenReady();\n        if (!playing && playButton != null) {\n            playButton.requestFocus();\n        } else if (playing && pauseButton != null) {\n            pauseButton.requestFocus();\n\n        }\n\n        if (!playing && playButtonLandScape != null) {\n            playButtonLandScape.requestFocus();\n        } else if (playing && pauseButtonLandScape != null) {\n            pauseButtonLandScape.requestFocus();\n\n        }\n    }\n\n    private void setButtonEnabled(boolean enabled, View view) {\n        if (view == null) {\n            return;\n        }\n        view.setEnabled(enabled);\n        view.setAlpha(enabled ? 1f : 0.3f);\n        view.setVisibility(VISIBLE);\n    }\n\n    private void previous() {\n        Timeline timeline = player.getCurrentTimeline();\n        if (timeline.isEmpty()) {\n            return;\n        }\n        int windowIndex = player.getCurrentWindowIndex();\n        timeline.getWindow(windowIndex, window);\n        int previousWindowIndex = player.getPreviousWindowIndex();\n        if (previousWindowIndex != C.INDEX_UNSET\n                && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS\n                || (window.isDynamic && !window.isSeekable))) {\n            seekTo(previousWindowIndex, C.TIME_UNSET);\n        } else {\n            seekTo(0);\n        }\n    }\n\n    private void next() {\n        Timeline timeline = player.getCurrentTimeline();\n        if (timeline.isEmpty()) {\n            return;\n        }\n        int windowIndex = player.getCurrentWindowIndex();\n        int nextWindowIndex = player.getNextWindowIndex();\n        if (nextWindowIndex != C.INDEX_UNSET) {\n            seekTo(nextWindowIndex, C.TIME_UNSET);\n        } else if (timeline.getWindow(windowIndex, window, false).isDynamic) {\n            seekTo(windowIndex, C.TIME_UNSET);\n        }\n    }\n\n    private void rewind() {\n        if (rewindMs <= 0) {\n            return;\n        }\n        seekTo(Math.max(player.getCurrentPosition() - rewindMs, 0));\n    }\n\n    private void fastForward() {\n        if (fastForwardMs <= 0) {\n            return;\n        }\n        long durationMs = player.getDuration();\n        long seekPositionMs = player.getCurrentPosition() + fastForwardMs;\n        if (durationMs != C.TIME_UNSET) {\n            seekPositionMs = Math.min(seekPositionMs, durationMs);\n        }\n        seekTo(seekPositionMs);\n    }\n\n    private void seekTo(long positionMs) {\n        seekTo(player.getCurrentWindowIndex(), positionMs);\n    }\n\n    private void seekTo(int windowIndex, long positionMs) {\n        boolean dispatched = controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs);\n        if (!dispatched) {\n            // The seek wasn't dispatched. If the progress bar was dragged by the user to perform the\n            // seek then it'll now be in the wrong position. Trigger a progress update to snap it back.\n            updateProgress();\n        }\n    }\n\n    private void seekToTimeBarPosition(long positionMs) {\n        int windowIndex;\n        Timeline timeline = player.getCurrentTimeline();\n        if (multiWindowTimeBar && !timeline.isEmpty()) {\n            int windowCount = timeline.getWindowCount();\n            windowIndex = 0;\n            while (true) {\n                long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs();\n                if (positionMs < windowDurationMs) {\n                    break;\n                } else if (windowIndex == windowCount - 1) {\n                    // Seeking past the end of the last window should seek to the end of the timeline.\n                    positionMs = windowDurationMs;\n                    break;\n                }\n                positionMs -= windowDurationMs;\n                windowIndex++;\n            }\n        } else {\n            windowIndex = player.getCurrentWindowIndex();\n        }\n        seekTo(windowIndex, positionMs);\n    }\n\n    @Override\n    public void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        sensorOrientation.enable();\n        isAttachedToWindow = true;\n        if (hideAtMs != C.TIME_UNSET) {\n            long delayMs = hideAtMs - SystemClock.uptimeMillis();\n            if (delayMs <= 0) {\n                hide();\n            } else {\n                postDelayed(hideAction, delayMs);\n            }\n        }\n        updateAll();\n    }\n\n    @Override\n    public void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        sensorOrientation.disable();\n        isAttachedToWindow = false;\n        removeCallbacks(updateProgressAction);\n        removeCallbacks(hideAction);\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n        return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event);\n    }\n\n    /**\n     * Called to process media key events. Any {@link KeyEvent} can be passed but only media key\n     * events will be handled.\n     *\n     * @param event A key event.\n     * @return Whether the key event was handled.\n     */\n    public boolean dispatchMediaKeyEvent(KeyEvent event) {\n        int keyCode = event.getKeyCode();\n        if (player == null || !isHandledMediaKey(keyCode)) {\n            return false;\n        }\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {\n                fastForward();\n            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) {\n                rewind();\n            } else if (event.getRepeatCount() == 0) {\n                switch (keyCode) {\n                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:\n                        controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady());\n                        break;\n                    case KeyEvent.KEYCODE_MEDIA_PLAY:\n                        controlDispatcher.dispatchSetPlayWhenReady(player, true);\n                        break;\n                    case KeyEvent.KEYCODE_MEDIA_PAUSE:\n                        controlDispatcher.dispatchSetPlayWhenReady(player, false);\n                        break;\n                    case KeyEvent.KEYCODE_MEDIA_NEXT:\n                        next();\n                        break;\n                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:\n                        previous();\n                        break;\n                    default:\n                        break;\n                }\n            }\n        }\n        return true;\n    }\n\n\n    public void setBackListener(ExoClickListener backListener) {\n        this.backListener = backListener;\n    }\n\n\n    public void setPortrait(boolean portrait) {\n        this.portrait = portrait;\n        showControllerByDisplayMode();\n    }\n\n    public boolean isPortrait() {\n        return portrait;\n    }\n\n    private void toggleControllerOrientation() {\n        if (orientationListener == null) {\n            setPortrait(!portrait);\n        } else {\n            changeOrientation(portrait ? SENSOR_LANDSCAPE : SENSOR_PORTRAIT);\n        }\n\n    }\n\n\n    public void setOrientationListener(OrientationListener orientationListener) {\n        this.orientationListener = orientationListener;\n    }\n\n\n    public void setMediaSource(ExoMediaSource exoMediaSource) {\n        if (exoPlayerVideoName != null) {\n            exoPlayerVideoName.setText(exoMediaSource.name());\n        }\n\n        if (exoPlayerVideoNameLandscape != null) {\n            exoPlayerVideoNameLandscape.setText(exoMediaSource.name());\n        }\n\n        if (centerError != null) {\n            centerError.setText(null);\n            centerError.setVisibility(GONE);\n        }\n    }\n\n\n    public void setControllerDisplayMode(int displayMode) {\n        this.displayMode = displayMode;\n        showControllerByDisplayMode();\n    }\n\n    private void showControllerByDisplayMode() {\n\n        if (exoPlayerControllerTop != null) {\n            boolean showByMode = (displayMode & CONTROLLER_MODE_TOP) == CONTROLLER_MODE_TOP;\n            if (portrait) {\n                int visibility = showByMode ? VISIBLE : INVISIBLE;\n                exoPlayerControllerTop.setVisibility(visibility);\n            } else {\n                exoPlayerControllerTop.setVisibility(INVISIBLE);\n            }\n\n        }\n\n        if (exoPlayerControllerTopLandscape != null) {\n            boolean showByMode = (displayMode & CONTROLLER_MODE_TOP_LANDSCAPE) == CONTROLLER_MODE_TOP_LANDSCAPE;\n            if (portrait) {\n                exoPlayerControllerTopLandscape.setVisibility(INVISIBLE);\n            } else {\n                int visibility = showByMode ? VISIBLE : INVISIBLE;\n                exoPlayerControllerTopLandscape.setVisibility(visibility);\n            }\n        }\n\n        if (exoPlayerControllerBottom != null) {\n            boolean showByMode = (displayMode & CONTROLLER_MODE_BOTTOM) == CONTROLLER_MODE_BOTTOM;\n            if (portrait) {\n                int visibility = showByMode ? VISIBLE : INVISIBLE;\n                exoPlayerControllerBottom.setVisibility(visibility);\n            } else {\n\n                exoPlayerControllerBottom.setVisibility(INVISIBLE);\n            }\n        }\n\n        if (exoPlayerControllerBottomLandscape != null) {\n            boolean showByMode = (displayMode & CONTROLLER_MODE_BOTTOM_LANDSCAPE) == CONTROLLER_MODE_BOTTOM_LANDSCAPE;\n            if (portrait) {\n                exoPlayerControllerBottomLandscape.setVisibility(INVISIBLE);\n            } else {\n                int visibility = showByMode ? VISIBLE : INVISIBLE;\n                exoPlayerControllerBottomLandscape.setVisibility(visibility);\n            }\n        }\n\n\n        if (qualityVisibilityCallback != null) {\n            qualityVisibilityCallback.shouldChangeVisibility(GONE);\n        }\n    }\n\n    private synchronized void changeOrientation(@OnOrientationChangedListener.SensorOrientationType int orientation) {\n        Context context = getContext();\n        Activity activity;\n        if (!(context instanceof Activity)) {\n            return;\n        }\n\n        if (orientationListener == null) {\n            return;\n        }\n\n\n        activity = (Activity) context;\n        switch (orientation) {\n            case SENSOR_PORTRAIT:\n                setPortrait(true);\n                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n                changeSystemUiVisibilityPortrait();\n                break;\n            case SENSOR_LANDSCAPE:\n                setPortrait(false);\n                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);\n                changeSystemUiVisibilityLandscape();\n                break;\n            case SENSOR_UNKNOWN:\n            default:\n                break;\n        }\n\n        orientationListener.onOrientationChanged(orientation);\n    }\n\n\n    private void changeSystemUiVisibilityPortrait() {\n        videoViewAccessor.attachVideoView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);\n    }\n\n    private void changeSystemUiVisibilityLandscape() {\n        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);\n        if (windowManager == null) {\n            return;\n        }\n\n        int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE\n                | View.SYSTEM_UI_FLAG_FULLSCREEN\n                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            flag |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;\n        }\n\n        videoViewAccessor.attachVideoView().setSystemUiVisibility(flag);\n    }\n\n\n    public void setVideoViewAccessor(VideoViewAccessor videoViewAccessor) {\n        this.videoViewAccessor = videoViewAccessor;\n    }\n\n    public void setVisibilityCallback(MultiQualitySelectorAdapter.VisibilityCallback qualityVisibilityCallback) {\n        this.qualityVisibilityCallback = qualityVisibilityCallback;\n    }\n\n    public void updateQualityDes(CharSequence qualityDes) {\n        if (exoPlayerCurrentQualityLandscape != null) {\n            exoPlayerCurrentQualityLandscape.setText(qualityDes);\n        }\n    }\n\n\n\n    /**\n     * add your view to controller\n     *\n     * @param customViewType the target view type\n     * @param customView     the view you want to add\n     * @param removeViews    remove all views in target view before add  if true\n     **/\n    public void addCustomView(@CustomViewType int customViewType, View customView, boolean removeViews) {\n        ViewGroup viewGroup = null;\n        if (customViewType == CUSTOM_VIEW_TOP && topCustomView != null) {\n            viewGroup = topCustomView;\n        } else if (customViewType == CUSTOM_VIEW_TOP_LANDSCAPE && topCustomView != null) {\n            viewGroup = topCustomViewLandscape;\n        } else if (customViewType == CUSTOM_VIEW_BOTTOM_LANDSCAPE && topCustomView != null) {\n            viewGroup = bottomCustomViewLandscape;\n        }\n\n        if (viewGroup != null) {\n            if (removeViews) {\n                viewGroup.removeAllViews();\n            }\n            viewGroup.addView(customView);\n        }\n\n    }\n\n    public void addCustomView(@CustomViewType int customViewType, View customView) {\n        addCustomView(customViewType, customView, false);\n    }\n\n    public void changeWidgetVisibility(int id,int visibility){\n       View view = findViewById(id);\n       if(view != null){\n           view.setVisibility(visibility);\n       }\n    }\n    private void showLoading(boolean isLoading) {\n        if(loadingBar == null ){\n            return;\n        }\n        if (isLoading) {\n            loadingBar.setVisibility(View.VISIBLE);\n        } else {\n            loadingBar.setVisibility(GONE);\n        }\n    }\n\n    public void showUtilHideCalled() {\n        if (!isVisible()) {\n            setVisibility(VISIBLE);\n            if (visibilityListener != null) {\n                visibilityListener.onVisibilityChange(getVisibility());\n            }\n            updateAll();\n        }\n    }\n\n    public void setGestureEnabled(boolean enabled){\n        if(centerInfoWrapper == null){\n            return;\n        }\n\n        if (videoGesture == null) {\n            return;\n        }\n\n        if(enabled){\n            videoGesture.enable();\n        }else {\n            videoGesture.disable();\n        }\n    }\n\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n\n        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {\n            if (backListener != null) {\n                if (!backListener.onClick(null, portrait)) {\n                    if (portrait) {\n                        return super.onKeyDown(keyCode, event);\n                    } else {\n                        changeOrientation(SENSOR_PORTRAIT);\n                        return true;\n                    }\n\n                }\n            }\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    private static boolean isHandledMediaKey(int keyCode) {\n        return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD\n                || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE\n                || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS;\n    }\n\n    /**\n     * Returns whether the specified {@code timeline} can be shown on a multi-window time bar.\n     *\n     * @param timeline The {@link Timeline} to check.\n     * @param window   A scratch {@link Timeline.Window} instance.\n     * @return Whether the specified timeline can be shown on a multi-window time bar.\n     */\n    private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) {\n        if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) {\n            return false;\n        }\n        int windowCount = timeline.getWindowCount();\n        for (int i = 0; i < windowCount; i++) {\n            if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private final class ComponentListener  implements\n            TimeBar.OnScrubListener, OnClickListener,Player.EventListener {\n\n        @Override\n        public void onScrubStart(TimeBar timeBar, long position) {\n            removeCallbacks(hideAction);\n            scrubbing = true;\n        }\n\n        @Override\n        public void onScrubMove(TimeBar timeBar, long position) {\n            if (positionView != null) {\n                positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));\n            }\n        }\n\n        @Override\n        public void onScrubStop(TimeBar timeBar, long position, boolean canceled) {\n            scrubbing = false;\n            if (!canceled && player != null) {\n                seekToTimeBarPosition(position);\n            }\n            hideAfterTimeout();\n        }\n\n        @Override\n        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {\n\n            if (playbackState != Player.STATE_IDLE && centerError != null && centerError.getVisibility() == VISIBLE) {\n                centerError.setVisibility(GONE);\n            }\n\n            if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_BUFFERING) {\n                removeCallbacks(hideAction);\n                showUtilHideCalled();\n                showLoading(true);\n            } else if (playbackState == Player.STATE_READY && player.getPlayWhenReady() || playbackState == Player.STATE_ENDED) {\n                showLoading(false);\n                hide();\n            }\n\n            updatePlayPauseButton();\n            updateProgress();\n        }\n\n        @Override\n        public void onRepeatModeChanged(int repeatMode) {\n            updateRepeatModeButton();\n            updateNavigation();\n        }\n\n        @Override\n        public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {\n            updateShuffleButton();\n            updateNavigation();\n        }\n\n        @Override\n        public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {\n            updateNavigation();\n            updateProgress();\n        }\n\n        @Override\n        public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {\n            if (manifest instanceof HlsManifest) {\n                HlsManifest hlsManifest = (HlsManifest) manifest;\n                isHls = !hlsManifest.mediaPlaylist.hasEndTag && hlsManifest.mediaPlaylist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;\n            } else {\n                isHls = false;\n            }\n\n\n            updateNavigation();\n            updateTimeBarMode();\n            updateProgress();\n        }\n\n\n        @Override\n        public void onPlayerError(ExoPlaybackException error) {\n            if(loadingBar  != null){\n                loadingBar.setVisibility(GONE);\n            }\n            if (centerError != null) {\n                String errorText = getResources().getString(R.string.player_error, error.type);\n                centerError.setText(errorText);\n                centerError.setVisibility(VISIBLE);\n            }\n        }\n\n        @Override\n        public void onClick(View view) {\n            if (player != null) {\n                if (nextButton == view) {\n                    next();\n                } else if (previousButton == view) {\n                    previous();\n                } else if (fastForwardButton == view) {\n                    fastForward();\n                } else if (rewindButton == view) {\n                    rewind();\n                } else if (playButton == view || playButtonLandScape == view) {\n                    controlDispatcher.dispatchSetPlayWhenReady(player, true);\n                } else if (pauseButton == view || pauseButtonLandScape == view) {\n                    controlDispatcher.dispatchSetPlayWhenReady(player, false);\n                } else if (repeatToggleButton == view) {\n                    controlDispatcher.dispatchSetRepeatMode(player, RepeatModeUtil.getNextRepeatMode(\n                            player.getRepeatMode(), repeatToggleModes));\n                } else if (shuffleButton == view) {\n                    controlDispatcher.dispatchSetShuffleModeEnabled(player, !player.getShuffleModeEnabled());\n                } else if (enterFullscreen == view) {\n                    changeOrientation(SENSOR_LANDSCAPE);\n                } else if (exitFullscreen == view) {\n                    changeOrientation(SENSOR_PORTRAIT);\n                } else if (exoPlayerVideoName == view || back == view) {\n                    if (backListener != null) {\n                        if (!backListener.onClick(view, portrait)) {\n                            changeOrientation(SENSOR_LANDSCAPE);\n                        }\n                    }\n                } else if (exoPlayerVideoNameLandscape == view || backLandscape == view) {\n                    if (backListener != null) {\n                        if (!backListener.onClick(view, portrait)) {\n                            changeOrientation(SENSOR_PORTRAIT);\n                        }\n                    }\n                } else if (centerInfoWrapper == view) {\n                    playOrPause();\n                } else if (exoPlayerCurrentQualityLandscape == view && qualityVisibilityCallback != null) {\n                    hide();\n                    qualityVisibilityCallback.shouldChangeVisibility(View.VISIBLE);\n                }\n            }\n            hideAfterTimeout();\n        }\n\n        long[] mHits = new long[2];\n\n        private void playOrPause() {\n\n            System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);\n            mHits[mHits.length - 1] = SystemClock.uptimeMillis();\n\n            if (500 > (SystemClock.uptimeMillis() - mHits[0])) {\n                controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady());\n            }\n\n        }\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/ui/ExoVideoView.java",
    "content": "package com.jarvanmo.exoplayerview.ui;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.media.AudioManager;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.util.AttributeSet;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.SurfaceView;\nimport android.view.TextureView;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.google.android.exoplayer2.C;\nimport com.google.android.exoplayer2.ControlDispatcher;\nimport com.google.android.exoplayer2.DefaultRenderersFactory;\nimport com.google.android.exoplayer2.Player;\nimport com.google.android.exoplayer2.SimpleExoPlayer;\nimport com.google.android.exoplayer2.metadata.Metadata;\nimport com.google.android.exoplayer2.metadata.id3.ApicFrame;\nimport com.google.android.exoplayer2.source.MediaSource;\nimport com.google.android.exoplayer2.source.TrackGroupArray;\nimport com.google.android.exoplayer2.text.Cue;\nimport com.google.android.exoplayer2.text.TextOutput;\nimport com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;\nimport com.google.android.exoplayer2.trackselection.DefaultTrackSelector;\nimport com.google.android.exoplayer2.trackselection.TrackSelection;\nimport com.google.android.exoplayer2.trackselection.TrackSelectionArray;\nimport com.google.android.exoplayer2.ui.AspectRatioFrameLayout;\nimport com.google.android.exoplayer2.ui.PlaybackControlView;\nimport com.google.android.exoplayer2.ui.SubtitleView;\nimport com.google.android.exoplayer2.upstream.BandwidthMeter;\nimport com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;\nimport com.google.android.exoplayer2.util.Assertions;\nimport com.google.android.exoplayer2.util.RepeatModeUtil;\nimport com.google.android.exoplayer2.util.Util;\nimport com.jarvanmo.exoplayerview.R;\nimport com.jarvanmo.exoplayerview.extension.MultiQualitySelectorAdapter;\nimport com.jarvanmo.exoplayerview.media.ExoMediaSource;\nimport com.jarvanmo.exoplayerview.media.MediaSourceCreator;\n\nimport java.util.List;\n\nimport static android.content.Context.AUDIO_SERVICE;\n\n/**\n * Created by mo on 16-11-7.\n *\n * @author mo\n */\n\n@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\npublic class ExoVideoView extends FrameLayout implements ExoVideoPlaybackControlView.VideoViewAccessor {\n\n\n    private static final int SURFACE_TYPE_NONE = 0;\n    private static final int SURFACE_TYPE_SURFACE_VIEW = 1;\n    private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;\n\n    private final AspectRatioFrameLayout contentFrame;\n    private final View shutterView;\n    private final View surfaceView;\n    private final ImageView artworkView;\n    private final SubtitleView subtitleView;\n    private final ExoVideoPlaybackControlView controller;\n    private final ComponentListener componentListener;\n    private final FrameLayout overlayFrameLayout;\n\n\n    private SimpleExoPlayer player;\n    private boolean useController;\n    private boolean useArtwork;\n    private Bitmap defaultArtwork;\n    private int controllerShowTimeoutMs;\n    private boolean controllerAutoShow;\n    private boolean controllerHideOnTouch;\n\n\n    private boolean pausedFromPlayer = false;\n\n    private boolean enableMultiQuality = true;\n\n    private MultiQualitySelectorAdapter.MultiQualitySelectorNavigator multiQualitySelectorNavigator;\n\n    private final AudioManager audioManager;\n    private AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() {\n        public void onAudioFocusChange(int focusChange) {\n            if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {\n                // Pause playback\n                pause();\n            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {\n                // Resume playback\n                resume();\n            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {\n                // audioManager.unregisterMediaButtonEventReceiver(RemoteControlReceiver);\n                audioManager.abandonAudioFocus(afChangeListener);\n                // Stop playback\n                stop();\n\n            }\n        }\n    };\n\n    private long lastPlayedPosition = 0L;\n    private long[] mHits = new long[2];\n\n    private int controllerBackgroundId = 0;\n\n    public ExoVideoView(Context context) {\n        this(context, null);\n    }\n\n    public ExoVideoView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public ExoVideoView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n\n        audioManager = (AudioManager) context.getApplicationContext().getSystemService(AUDIO_SERVICE);\n\n\n        if (isInEditMode()) {\n            contentFrame = null;\n            shutterView = null;\n            surfaceView = null;\n            artworkView = null;\n            subtitleView = null;\n            controller = null;\n            componentListener = null;\n            overlayFrameLayout = null;\n            ImageView logo = new ImageView(context);\n            if (Util.SDK_INT >= 23) {\n                configureEditModeLogoV23(getResources(), logo);\n            } else {\n                configureEditModeLogo(getResources(), logo);\n            }\n            addView(logo);\n            return;\n        }\n\n        boolean shutterColorSet = false;\n        int shutterColor = 0;\n        int playerLayoutId = R.layout.exo_video_view;\n        boolean useArtwork = true;\n        int defaultArtworkId = 0;\n        boolean useController = true;\n        int surfaceType = SURFACE_TYPE_SURFACE_VIEW;\n        int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;\n        int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS;\n        boolean controllerHideOnTouch = true;\n        boolean controllerAutoShow = true;\n        if (attrs != null) {\n            TypedArray a = context.getTheme().obtainStyledAttributes(attrs,\n                    R.styleable.ExoVideoView, 0, 0);\n            try {\n                shutterColorSet = a.hasValue(R.styleable.ExoVideoView_shutter_background_color);\n                shutterColor = a.getColor(R.styleable.ExoVideoView_shutter_background_color,\n                        shutterColor);\n                playerLayoutId = a.getResourceId(R.styleable.ExoVideoView_player_layout_id,\n                        playerLayoutId);\n                useArtwork = a.getBoolean(R.styleable.ExoVideoView_use_artwork, useArtwork);\n                defaultArtworkId = a.getResourceId(R.styleable.ExoVideoView_default_artwork,\n                        defaultArtworkId);\n                useController = a.getBoolean(R.styleable.ExoVideoView_use_controller, useController);\n                surfaceType = a.getInt(R.styleable.ExoVideoView_surface_type, surfaceType);\n                resizeMode = a.getInt(R.styleable.ExoVideoView_resize_mode, resizeMode);\n                controllerShowTimeoutMs = a.getInt(R.styleable.ExoVideoView_show_timeout,\n                        controllerShowTimeoutMs);\n                controllerHideOnTouch = a.getBoolean(R.styleable.ExoVideoView_hide_on_touch,\n                        controllerHideOnTouch);\n                controllerAutoShow = a.getBoolean(R.styleable.ExoVideoView_auto_show,\n                        controllerAutoShow);\n                enableMultiQuality = a.getBoolean(R.styleable.ExoVideoView_enable_multi_quality, true);\n\n                controllerBackgroundId = a.getResourceId(R.styleable.ExoVideoView_controller_background, 0);\n            } finally {\n                a.recycle();\n            }\n        }\n\n        LayoutInflater.from(context).inflate(playerLayoutId, this);\n        componentListener = new ComponentListener();\n        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);\n\n        // Content frame.\n        contentFrame = findViewById(R.id.exo_player_content_frame);\n        if (contentFrame != null) {\n            setResizeModeRaw(contentFrame, resizeMode);\n        }\n\n        // Shutter view.\n        shutterView = findViewById(R.id.exo_player_shutter);\n        if (shutterView != null && shutterColorSet) {\n            shutterView.setBackgroundColor(shutterColor);\n        }\n\n        // Create a surface view and insert it into the content frame, if there is one.\n        if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {\n            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);\n            surfaceView = surfaceType == SURFACE_TYPE_TEXTURE_VIEW ? new TextureView(context)\n                    : new SurfaceView(context);\n            surfaceView.setLayoutParams(params);\n            contentFrame.addView(surfaceView, 0);\n        } else {\n            surfaceView = null;\n        }\n\n        // Overlay frame layout.\n        overlayFrameLayout = findViewById(R.id.exo_player_overlay);\n\n        // Artwork view.\n        artworkView = findViewById(R.id.exo_player_artwork);\n        this.useArtwork = useArtwork && artworkView != null;\n        if (defaultArtworkId != 0) {\n            defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtworkId);\n            setArtworkFromBitmap(defaultArtwork);\n        }\n\n        // Subtitle view.\n        subtitleView = findViewById(R.id.exo_player_subtitles);\n        if (subtitleView != null) {\n            subtitleView.setUserDefaultStyle();\n            subtitleView.setUserDefaultTextSize();\n        }\n\n\n        // Playback control view.\n        ExoVideoPlaybackControlView customController = findViewById(R.id.exo_player_controller);\n        View controllerPlaceholder = findViewById(R.id.exo_player_controller_placeholder);\n        if (customController != null) {\n            this.controller = customController;\n        } else if (controllerPlaceholder != null) {\n            // Propagate attrs as playbackAttrs so that PlaybackControlView's custom attributes are\n            // transferred, but standard FrameLayout attributes (e.g. background) are not.\n            this.controller = new ExoVideoPlaybackControlView(context, null, 0, attrs);\n            controller.setLayoutParams(controllerPlaceholder.getLayoutParams());\n            ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());\n            int controllerIndex = parent.indexOfChild(controllerPlaceholder);\n            parent.removeView(controllerPlaceholder);\n            parent.addView(controller, controllerIndex);\n        } else {\n            this.controller = null;\n        }\n        this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;\n        this.controllerHideOnTouch = controllerHideOnTouch;\n        this.controllerAutoShow = controllerAutoShow;\n        this.useController = useController && controller != null;\n\n\n        if (useController && controller != null) {\n            controller.show();\n            controller.setVideoViewAccessor(this);\n        }\n        setKeepScreenOn(true);\n    }\n\n    /**\n     * Switches the view targeted by a given {@link SimpleExoPlayer}.\n     *\n     * @param player        The player whose target view is being switched.\n     * @param oldPlayerView The old view to detach from the player.\n     * @param newPlayerView The new view to attach to the player.\n     */\n    public static void switchTargetView(@NonNull SimpleExoPlayer player,\n                                        @Nullable ExoVideoView oldPlayerView, @Nullable ExoVideoView newPlayerView) {\n        if (oldPlayerView == newPlayerView) {\n            return;\n        }\n        // We attach the new view before detaching the old one because this ordering allows the player\n        // to swap directly from one surface to another, without transitioning through a state where no\n        // surface is attached. This is significantly more efficient and achieves a more seamless\n        // transition when using platform provided video decoders.\n        if (newPlayerView != null) {\n            newPlayerView.setPlayer(player);\n        }\n        if (oldPlayerView != null) {\n            oldPlayerView.setPlayer(null);\n        }\n    }\n\n    /**\n     * Returns the player currently set on this view, or null if no player is set.\n     */\n    public SimpleExoPlayer getPlayer() {\n        return player;\n    }\n\n    /**\n     * Set the {@link SimpleExoPlayer} to use.\n     * <p>\n     * To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to\n     * use {@link #switchTargetView(SimpleExoPlayer, ExoVideoView, ExoVideoView)} rather\n     * than this method. If you do wish to use this method directly, be sure to attach the player to\n     * the new view <em>before</em> calling {@code setPlayer(null)} to detach it from the old one.\n     * This ordering is significantly more efficient and may allow for more seamless transitions.\n     *\n     * @param player The {@link SimpleExoPlayer} to use.\n     */\n    public void setPlayer(SimpleExoPlayer player) {\n        if (this.player == player) {\n            return;\n        }\n        if (this.player != null) {\n            this.player.removeListener(componentListener);\n            this.player.removeTextOutput(componentListener);\n            this.player.removeVideoListener(componentListener);\n            if (surfaceView instanceof TextureView) {\n                this.player.clearVideoTextureView((TextureView) surfaceView);\n            } else if (surfaceView instanceof SurfaceView) {\n                this.player.clearVideoSurfaceView((SurfaceView) surfaceView);\n            }\n        }\n        this.player = player;\n        if (useController) {\n            controller.setPlayer(player);\n        }\n        if (shutterView != null) {\n            shutterView.setVisibility(VISIBLE);\n        }\n        if (player != null) {\n            if (surfaceView instanceof TextureView) {\n                player.setVideoTextureView((TextureView) surfaceView);\n            } else if (surfaceView instanceof SurfaceView) {\n                player.setVideoSurfaceView((SurfaceView) surfaceView);\n            }\n            player.addVideoListener(componentListener);\n            player.addTextOutput(componentListener);\n            player.addListener(componentListener);\n            maybeShowController(false);\n            updateForCurrentTrackSelections();\n        } else {\n            hideController();\n            hideArtwork();\n        }\n    }\n\n    @Override\n    public void setVisibility(int visibility) {\n        super.setVisibility(visibility);\n        if (surfaceView instanceof SurfaceView) {\n            // Work around https://github.com/google/ExoPlayer/issues/3160.\n            surfaceView.setVisibility(visibility);\n        }\n    }\n\n    /**\n     * Sets the resize mode.\n     *\n     * @param resizeMode The resize mode.\n     */\n    public void setResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {\n        Assertions.checkState(contentFrame != null);\n        contentFrame.setResizeMode(resizeMode);\n    }\n\n    /**\n     * Returns whether artwork is displayed if present in the media.\n     */\n    public boolean getUseArtwork() {\n        return useArtwork;\n    }\n\n    /**\n     * Sets whether artwork is displayed if present in the media.\n     *\n     * @param useArtwork Whether artwork is displayed.\n     */\n    public void setUseArtwork(boolean useArtwork) {\n        Assertions.checkState(!useArtwork || artworkView != null);\n        if (this.useArtwork != useArtwork) {\n            this.useArtwork = useArtwork;\n            updateForCurrentTrackSelections();\n        }\n    }\n\n    /**\n     * Returns the default artwork to display.\n     */\n    public Bitmap getDefaultArtwork() {\n        return defaultArtwork;\n    }\n\n    /**\n     * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is\n     * present in the media.\n     *\n     * @param defaultArtwork the default artwork to display.\n     */\n    public void setDefaultArtwork(Bitmap defaultArtwork) {\n        if (this.defaultArtwork != defaultArtwork) {\n            this.defaultArtwork = defaultArtwork;\n            updateForCurrentTrackSelections();\n        }\n    }\n\n    /**\n     * Returns whether the playback controls can be shown.\n     */\n    public boolean getUseController() {\n        return useController;\n    }\n\n    /**\n     * Sets whether the playback controls can be shown. If set to {@code false} the playback controls\n     * are never visible and are disconnected from the player.\n     *\n     * @param useController Whether the playback controls can be shown.\n     */\n    public void setUseController(boolean useController) {\n        Assertions.checkState(!useController || controller != null);\n        if (this.useController == useController) {\n            return;\n        }\n        this.useController = useController;\n        if (useController) {\n            controller.setPlayer(player);\n        } else if (controller != null) {\n            controller.hide();\n            controller.setPlayer(null);\n        }\n    }\n\n    /**\n     * Sets the background color of the {@code exo_shutter} view.\n     *\n     * @param color The background color.\n     */\n    public void setShutterBackgroundColor(int color) {\n        if (shutterView != null) {\n            shutterView.setBackgroundColor(color);\n        }\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        if (audioManager != null) {\n            requestAudioFocus();\n        }\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        if (audioManager != null) {\n            audioManager.abandonAudioFocus(afChangeListener);\n        }\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n        if (player != null && player.isPlayingAd()) {\n            // Focus any overlay UI now, in case it's provided by a WebView whose contents may update\n            // dynamically. This is needed to make the \"Skip ad\" button focused on Android TV when using\n            // IMA [Internal: b/62371030].\n            overlayFrameLayout.requestFocus();\n            return super.dispatchKeyEvent(event);\n        }\n        boolean isDpadWhenControlHidden = isDpadKey(event.getKeyCode()) && useController\n                && !controller.isVisible();\n        maybeShowController(true);\n        return isDpadWhenControlHidden || dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event);\n    }\n\n    /**\n     * Called to process media key events. Any {@link KeyEvent} can be passed but only media key\n     * events will be handled. Does nothing if playback controls are disabled.\n     *\n     * @param event A key event.\n     * @return Whether the key event was handled.\n     */\n    public boolean dispatchMediaKeyEvent(KeyEvent event) {\n        return useController && controller.dispatchMediaKeyEvent(event);\n    }\n\n    /**\n     * Shows the playback controls. Does nothing if playback controls are disabled.\n     * <p>\n     * <p>The playback controls are automatically hidden during playback after\n     * {{@link #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not\n     * started yet, is paused, has ended or failed.\n     */\n    public void showController() {\n        showController(shouldShowControllerIndefinitely());\n    }\n\n    /**\n     * Hides the playback controls. Does nothing if playback controls are disabled.\n     */\n    public void hideController() {\n        if (controller != null) {\n            controller.hide();\n        }\n    }\n\n    /**\n     * Returns the playback controls timeout. The playback controls are automatically hidden after\n     * this duration of time has elapsed without user input and with playback or buffering in\n     * progress.\n     *\n     * @return The timeout in milliseconds. A non-positive value will cause the controller to remain\n     * visible indefinitely.\n     */\n    public int getControllerShowTimeoutMs() {\n        return controllerShowTimeoutMs;\n    }\n\n    /**\n     * Sets the playback controls timeout. The playback controls are automatically hidden after this\n     * duration of time has elapsed without user input and with playback or buffering in progress.\n     *\n     * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause\n     *                                the controller to remain visible indefinitely.\n     */\n    public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) {\n        Assertions.checkState(controller != null);\n        this.controllerShowTimeoutMs = controllerShowTimeoutMs;\n    }\n\n    /**\n     * Returns whether the playback controls are hidden by touch events.\n     */\n    public boolean getControllerHideOnTouch() {\n        return controllerHideOnTouch;\n    }\n\n    /**\n     * Sets whether the playback controls are hidden by touch events.\n     *\n     * @param controllerHideOnTouch Whether the playback controls are hidden by touch events.\n     */\n    public void setControllerHideOnTouch(boolean controllerHideOnTouch) {\n        Assertions.checkState(controller != null);\n        this.controllerHideOnTouch = controllerHideOnTouch;\n    }\n\n    /**\n     * Returns whether the playback controls are automatically shown when playback starts, pauses,\n     * ends, or fails. If set to false, the playback controls can be manually operated with {@link\n     * #showController()} and {@link #hideController()}.\n     */\n    public boolean getControllerAutoShow() {\n        return controllerAutoShow;\n    }\n\n    /**\n     * Sets whether the playback controls are automatically shown when playback starts, pauses, ends,\n     * or fails. If set to false, the playback controls can be manually operated with {@link\n     * #showController()} and {@link #hideController()}.\n     *\n     * @param controllerAutoShow Whether the playback controls are allowed to show automatically.\n     */\n    public void setControllerAutoShow(boolean controllerAutoShow) {\n        this.controllerAutoShow = controllerAutoShow;\n    }\n\n    /**\n     * Set the {@link PlaybackControlView.VisibilityListener}.\n     *\n     * @param listener The listener to be notified about visibility changes.\n     */\n    public void setControllerVisibilityListener(ExoVideoPlaybackControlView.VisibilityListener listener) {\n        Assertions.checkState(controller != null);\n        controller.setVisibilityListener(listener);\n    }\n\n    /**\n     * Sets the {@link ControlDispatcher}.\n     *\n     * @param controlDispatcher The {@link ControlDispatcher}, or null to use\n     *                          {@link PlaybackControlView.DefaultControlDispatcher}.\n     */\n    public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) {\n        Assertions.checkState(controller != null);\n        controller.setControlDispatcher(controlDispatcher);\n    }\n\n    /**\n     * Sets the rewind increment in milliseconds.\n     *\n     * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the\n     *                 rewind button to be disabled.\n     */\n    public void setRewindIncrementMs(int rewindMs) {\n        Assertions.checkState(controller != null);\n        controller.setRewindIncrementMs(rewindMs);\n    }\n\n    /**\n     * Sets the fast forward increment in milliseconds.\n     *\n     * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will\n     *                      cause the fast forward button to be disabled.\n     */\n    public void setFastForwardIncrementMs(int fastForwardMs) {\n        Assertions.checkState(controller != null);\n        controller.setFastForwardIncrementMs(fastForwardMs);\n    }\n\n    /**\n     * Sets which repeat toggle modes are enabled.\n     *\n     * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.\n     */\n    public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {\n        Assertions.checkState(controller != null);\n        controller.setRepeatToggleModes(repeatToggleModes);\n    }\n\n    /**\n     * Sets whether the shuffle button is shown.\n     *\n     * @param showShuffleButton Whether the shuffle button is shown.\n     */\n    public void setShowShuffleButton(boolean showShuffleButton) {\n        Assertions.checkState(controller != null);\n        controller.setShowShuffleButton(showShuffleButton);\n    }\n\n    /**\n     * Sets whether the time bar should show all windows, as opposed to just the current one.\n     *\n     * @param showMultiWindowTimeBar Whether to show all windows.\n     */\n    public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {\n        Assertions.checkState(controller != null);\n        controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);\n    }\n\n    /**\n     * Gets the view onto which video is rendered. This is a:\n     * <ul>\n     * <li>{@link SurfaceView} by default, or if the {@code surface_type} attribute is set to\n     * {@code surface_view}.</li>\n     * <li>{@link TextureView} if {@code surface_type} is {@code texture_view}.</li>\n     * <li>{@code null} if {@code surface_type} is {@code none}.</li>\n     * </ul>\n     *\n     * @return The {@link SurfaceView}, {@link TextureView} or {@code null}.\n     */\n    public View getVideoSurfaceView() {\n        return surfaceView;\n    }\n\n    /**\n     * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of\n     * the player.\n     *\n     * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and\n     * the overlay is not present.\n     */\n    public FrameLayout getOverlayFrameLayout() {\n        return overlayFrameLayout;\n    }\n\n    /**\n     * Gets the {@link SubtitleView}.\n     *\n     * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the\n     * subtitle view is not present.\n     */\n    public SubtitleView getSubtitleView() {\n        return subtitleView;\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n\n        if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {\n            return false;\n        }\n        if (enableMultiQuality) {\n            View v = overlayFrameLayout.findViewById(R.id.exo_player_quality_container);\n            if (v != null && overlayFrameLayout.getVisibility() == VISIBLE) {\n                return true;\n            }\n\n        }\n\n        if (!controller.isVisible()) {\n            maybeShowController(true);\n        } else if (controllerHideOnTouch) {\n            controller.hide();\n        }\n        return true;\n    }\n\n    @Override\n    public boolean onTrackballEvent(MotionEvent ev) {\n        if (!useController || player == null) {\n            return false;\n        }\n        maybeShowController(true);\n        return true;\n    }\n\n    /**\n     * Shows the playback controls, but only if forced or shown indefinitely.\n     */\n    private void maybeShowController(boolean isForced) {\n        if (isPlayingAd()) {\n            // Never show the controller if an ad is currently playing.\n            return;\n        }\n        if (useController) {\n            boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;\n            boolean shouldShowIndefinitely = shouldShowControllerIndefinitely();\n            if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) {\n                showController(shouldShowIndefinitely);\n            }\n        }\n    }\n\n    private boolean shouldShowControllerIndefinitely() {\n        if (player == null) {\n            return true;\n        }\n        int playbackState = player.getPlaybackState();\n        return controllerAutoShow && (playbackState == Player.STATE_IDLE\n                || playbackState == Player.STATE_ENDED || !player.getPlayWhenReady());\n    }\n\n    private void showController(boolean showIndefinitely) {\n        if (!useController) {\n            return;\n        }\n        controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs);\n        controller.show();\n    }\n\n    private boolean isPlayingAd() {\n        return player != null && player.isPlayingAd() && player.getPlayWhenReady();\n    }\n\n    private void updateForCurrentTrackSelections() {\n        if (player == null) {\n            return;\n        }\n        TrackSelectionArray selections = player.getCurrentTrackSelections();\n        for (int i = 0; i < selections.length; i++) {\n            if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) {\n                // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in\n                // onRenderedFirstFrame().\n                hideArtwork();\n                return;\n            }\n        }\n        // Video disabled so the shutter must be closed.\n        if (shutterView != null) {\n            shutterView.setVisibility(VISIBLE);\n        }\n        // Display artwork if enabled and available, else hide it.\n        if (useArtwork) {\n            for (int i = 0; i < selections.length; i++) {\n                TrackSelection selection = selections.get(i);\n                if (selection != null) {\n                    for (int j = 0; j < selection.length(); j++) {\n                        Metadata metadata = selection.getFormat(j).metadata;\n                        if (metadata != null && setArtworkFromMetadata(metadata)) {\n                            return;\n                        }\n                    }\n                }\n            }\n            if (setArtworkFromBitmap(defaultArtwork)) {\n                return;\n            }\n        }\n        // Artwork disabled or unavailable.\n        hideArtwork();\n    }\n\n    private boolean setArtworkFromMetadata(Metadata metadata) {\n        for (int i = 0; i < metadata.length(); i++) {\n            Metadata.Entry metadataEntry = metadata.get(i);\n            if (metadataEntry instanceof ApicFrame) {\n                byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData;\n                Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);\n                return setArtworkFromBitmap(bitmap);\n            }\n        }\n        return false;\n    }\n\n    private boolean setArtworkFromBitmap(Bitmap bitmap) {\n        if (bitmap != null) {\n            int bitmapWidth = bitmap.getWidth();\n            int bitmapHeight = bitmap.getHeight();\n            if (bitmapWidth > 0 && bitmapHeight > 0) {\n                if (contentFrame != null) {\n                    contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);\n                }\n                artworkView.setImageBitmap(bitmap);\n                artworkView.setVisibility(VISIBLE);\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private void hideArtwork() {\n        if (artworkView != null) {\n            artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference.\n            artworkView.setVisibility(INVISIBLE);\n        }\n    }\n\n    @TargetApi(23)\n    private static void configureEditModeLogoV23(Resources resources, ImageView logo) {\n        logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null));\n        logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null));\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static void configureEditModeLogo(Resources resources, ImageView logo) {\n        logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo));\n        logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color));\n    }\n\n    @SuppressWarnings(\"ResourceType\")\n    private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) {\n        aspectRatioFrame.setResizeMode(resizeMode);\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    private boolean isDpadKey(int keyCode) {\n        return keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT\n                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT\n                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT\n                || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT\n                || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;\n    }\n\n\n    public void play(ExoMediaSource mediaSource) {\n        play(mediaSource, true, C.TIME_UNSET);\n    }\n\n    public void play(ExoMediaSource mediaSource, boolean playWhenReady) {\n        play(mediaSource, playWhenReady, C.TIME_UNSET);\n    }\n\n    public void play(ExoMediaSource mediaSource, long where) {\n        play(mediaSource, true, where);\n    }\n\n    public void play(ExoMediaSource mediaSource, boolean playWhenReady, long where) {\n        releasePlayer();\n        MediaSourceCreator creator = new MediaSourceCreator(getContext().getApplicationContext());\n        createExoPlayer(creator);\n        if (controller != null) {\n            controller.setMediaSource(mediaSource);\n        }\n\n        if (enableMultiQuality && mediaSource.qualities() != null && !mediaSource.qualities().isEmpty()) {\n            addMultiQualitySelector(mediaSource);\n        }\n\n\n        playInternal(mediaSource, playWhenReady, where, creator);\n    }\n\n    public void pause() {\n        if (player == null) {\n            return;\n        }\n\n        if (player.getPlayWhenReady()) {\n            lastPlayedPosition = player.getCurrentPosition();\n            player.setPlayWhenReady(false);\n            pausedFromPlayer = false;\n        } else {\n            pausedFromPlayer = true;\n        }\n    }\n\n    public void resume() {\n        if (player == null) {\n            return;\n        }\n\n        if (player.getPlayWhenReady()) {\n            return;\n        }\n\n        if (!pausedFromPlayer) {\n            player.seekTo(lastPlayedPosition - 500 < 0 ? 0 : lastPlayedPosition - 500);\n            player.setPlayWhenReady(true);\n        }\n\n    }\n\n    public void stop() {\n        if (player != null) {\n            player.stop();\n        }\n    }\n\n    private void playInternal(ExoMediaSource mediaSource, boolean playWhenReady, long where, MediaSourceCreator creator) {\n        MediaSource tmp = creator.buildMediaSource(mediaSource.uri(), mediaSource.extension());\n        player.prepare(tmp);\n        if (where == C.TIME_UNSET) {\n            player.seekTo(0L);\n        } else {\n            player.seekTo(where);\n        }\n\n        player.setPlayWhenReady(requestAudioFocus() && playWhenReady);\n    }\n\n    private void createExoPlayer(MediaSourceCreator creator) {\n        if (player != null) {\n            releasePlayer();\n        }\n\n        DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext());\n        renderersFactory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);\n        SimpleExoPlayer.Builder builder = new SimpleExoPlayer.Builder(getContext(), renderersFactory);\n        TrackSelection.Factory adaptiveTrackSelectionFactory =\n                new AdaptiveTrackSelection.Factory();\n        DefaultTrackSelector trackSelector = new DefaultTrackSelector(getContext(),new AdaptiveTrackSelection.Factory());\n        builder.setTrackSelector(new DefaultTrackSelector(getContext(),adaptiveTrackSelectionFactory));\n        builder.setTrackSelector(trackSelector);\n        builder.setBandwidthMeter(new DefaultBandwidthMeter.Builder(getContext()).build());\n        SimpleExoPlayer internalPlayer = builder.build();\n        internalPlayer.addListener(componentListener);\n        internalPlayer.addListener(creator.getEventLogger());\n        internalPlayer.addMetadataOutput(creator.getEventLogger());\n        setPlayer(internalPlayer);\n    }\n\n\n    public void releasePlayer() {\n        if (player != null) {\n            player.release();\n        }\n\n        player = null;\n    }\n\n\n    private boolean requestAudioFocus() {\n\n        if (audioManager == null) {\n            return true;\n        }\n        // Request audio focus for playback\n        int result = audioManager.requestAudioFocus(afChangeListener,\n                // Use the music stream.\n                AudioManager.STREAM_MUSIC,\n                // Request permanent focus.\n                AudioManager.AUDIOFOCUS_GAIN);\n        return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;\n    }\n\n\n    public void setBackListener(ExoVideoPlaybackControlView.ExoClickListener backListener) {\n        if (controller != null) {\n            controller.setBackListener(backListener);\n        }\n    }\n\n\n    public void setOrientationListener(ExoVideoPlaybackControlView.OrientationListener orientationListener) {\n        if (controller != null) {\n            controller.setOrientationListener(orientationListener);\n        }\n    }\n\n\n    public void setMultiQualitySelectorNavigator(MultiQualitySelectorAdapter.MultiQualitySelectorNavigator navigator) {\n        this.multiQualitySelectorNavigator = navigator;\n    }\n\n    public boolean isPortrait() {\n        return controller != null && controller.isPortrait();\n    }\n\n    public void setPortrait(boolean portrait) {\n        if (controller != null) {\n            controller.setPortrait(portrait);\n        }\n\n    }\n\n    public void changeWidgetVisibility(int id, int visibility) {\n        if (controller != null) {\n            controller.changeWidgetVisibility(id, visibility);\n        }\n    }\n\n    public void setControllerDisplayMode(int displayMode) {\n        if (controller != null) {\n            controller.setControllerDisplayMode(displayMode);\n        }\n    }\n\n\n    private void addMultiQualitySelector(ExoMediaSource mediaSource) {\n        for (ExoMediaSource.Quality quality : mediaSource.qualities()) {\n            if (TextUtils.equals(quality.getUri().toString(), mediaSource.uri().toString())) {\n                if (controller != null) {\n                    controller.updateQualityDes(quality.getDisplayName());\n                }\n                break;\n            }\n        }\n\n        if (controller != null) {\n            controller.setVisibilityCallback(visibility -> {\n                overlayFrameLayout.setVisibility(visibility);\n                if (visibility != VISIBLE) {\n                    controller.show();\n                }\n\n            });\n        }\n\n        MultiQualitySelectorAdapter adapter = new MultiQualitySelectorAdapter(mediaSource.qualities(), quality -> {\n            if (player == null) {\n                return false;\n            }\n\n\n            if (multiQualitySelectorNavigator == null || !multiQualitySelectorNavigator.onQualitySelected(quality)) {\n                long current = player.getCurrentPosition();\n                boolean playWhenReady = player.getPlayWhenReady();\n                MediaSourceCreator creator = new MediaSourceCreator(getContext().getApplicationContext());\n                MediaSource tmp = creator.buildMediaSource(quality.getUri(), null);\n                player.setPlayWhenReady(requestAudioFocus() && playWhenReady);\n                player.prepare(tmp);\n                player.seekTo(current);\n            }\n\n            if (controller != null) {\n                controller.updateQualityDes(quality.getDisplayName());\n            }\n\n            overlayFrameLayout.setVisibility(GONE);\n\n            return false;\n\n        });\n\n\n        overlayFrameLayout.removeAllViews();\n        View view = LayoutInflater.from(getContext()).inflate(R.layout.exo_player_quality_selector, null, false);\n        overlayFrameLayout.setVisibility(View.GONE);\n\n        RecyclerView container = view.findViewById(R.id.exo_player_quality_container);\n        container.setAdapter(adapter);\n        container.setLayoutManager(new LinearLayoutManager(getContext()));\n\n        view.setOnClickListener(v -> overlayFrameLayout.setVisibility(GONE));\n\n        View containerWrapper = view.findViewById(R.id.containerWrapper);\n        if (controllerBackgroundId != 0) {\n            containerWrapper.setBackgroundResource(controllerBackgroundId);\n        }\n\n        overlayFrameLayout.addView(view);\n    }\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        if (controller != null) {\n            return controller.onKeyDown(keyCode, event);\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @Override\n    public View attachVideoView() {\n        return this;\n    }\n\n    /**\n     * add your view to controller\n     *\n     * @param customViewType the target view type\n     * @param customView     the view you want to add\n     * @param removeViews    remove all views in target view before add if true\n     **/\n    public void addCustomView(@ExoVideoPlaybackControlView.CustomViewType int customViewType, View customView, boolean removeViews) {\n        if (useController && controller != null) {\n            controller.addCustomView(customViewType, customView, removeViews);\n        }\n    }\n\n    public void addCustomView(@ExoVideoPlaybackControlView.CustomViewType int customViewType, View customView) {\n        addCustomView(customViewType, customView, false);\n    }\n\n    public void setGestureEnabled(boolean enabled) {\n        if (controller != null) {\n            controller.setGestureEnabled(enabled);\n        }\n    }\n\n    private final class ComponentListener implements TextOutput, Player.EventListener,\n            com.google.android.exoplayer2.video.VideoListener {\n\n        // TextOutput implementation\n\n        @Override\n        public void onCues(List<Cue> cues) {\n            if (subtitleView != null) {\n                subtitleView.onCues(cues);\n            }\n        }\n\n        // SimpleExoPlayer.VideoInfoListener implementation\n\n        @Override\n        public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,\n                                       float pixelWidthHeightRatio) {\n            if (contentFrame != null) {\n                float aspectRatio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height;\n                contentFrame.setAspectRatio(aspectRatio);\n            }\n        }\n\n        @Override\n        public void onRenderedFirstFrame() {\n            if (shutterView != null) {\n                shutterView.setVisibility(INVISIBLE);\n            }\n        }\n\n        @Override\n        public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) {\n            updateForCurrentTrackSelections();\n        }\n\n        // Player.EventListener implementation\n\n        @Override\n        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {\n            if (isPlayingAd()) {\n                hideController();\n            } else {\n                maybeShowController(false);\n            }\n        }\n\n        @Override\n        public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {\n            if (isPlayingAd()) {\n                hideController();\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/util/AndroidUtil.java",
    "content": "/*****************************************************************************\n * AndroidUtil.java\n *****************************************************************************\n * Copyright © 2015 VLC authors, VideoLAN and VideoLabs\n *\n * This program is free software; you can redistribute it and/or modify it\n * under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation; either version 2.1 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.\n *****************************************************************************/\n\npackage com.jarvanmo.exoplayerview.util;\n\nimport android.net.Uri;\nimport android.os.Build;\n\nimport java.io.File;\n\npublic class AndroidUtil {\n\n\n    public static boolean isJellyBeanMR1OrLater() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;\n    }\n\n    public static boolean isJellyBeanMR2OrLater() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;\n    }\n\n    public static boolean isKitKatOrLater() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;\n    }\n\n    public static boolean isLolliPopOrLater() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;\n    }\n\n    public static boolean isMarshMallowOrLater() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;\n    }\n\n    public static File UriToFile(Uri uri) {\n        return new File(uri.getPath().replaceFirst(\"file://\", \"\"));\n    }\n\n    /**\n     * Quickly converts path to URIs, which are mandatory in libVLC.\n     *\n     * @param path The path to be converted.\n     * @return A URI representation of path\n     */\n    public static Uri PathToUri(String path) {\n        return Uri.fromFile(new File(path));\n    }\n\n    public static Uri LocationToUri(String location) {\n        Uri uri = Uri.parse(location);\n        if (uri.getScheme() == null)\n            throw new IllegalArgumentException(\"location has no scheme\");\n        return uri;\n    }\n\n    public static Uri FileToUri(File file) {\n        return Uri.fromFile(file);\n    }\n}"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/util/Permissions.java",
    "content": "/*\n * *************************************************************************\n *  Permissions.java\n * **************************************************************************\n *  Copyright © 2015 VLC authors and VideoLAN\n *  Author: Geoffrey Métais\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.\n *  ***************************************************************************\n */\n\npackage com.jarvanmo.exoplayerview.util;\n\nimport android.Manifest;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.provider.Settings;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\n\npublic class Permissions {\n\n    public static final int PERMISSION_STORAGE_TAG = 255;\n    public static final int PERMISSION_SETTINGS_TAG = 254;\n\n\n    public static final int PERMISSION_SYSTEM_RINGTONE = 42;\n    public static final int PERMISSION_SYSTEM_BRIGHTNESS = 43;\n    public static final int PERMISSION_SYSTEM_DRAW_OVRLAYS = 44;\n\n    /*\n     * Marshmallow permission system management\n     */\n\n    @TargetApi(Build.VERSION_CODES.M)\n    public static boolean canDrawOverlays(Context context) {\n        return !AndroidUtil.isMarshMallowOrLater() || Settings.canDrawOverlays(context);\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    public static boolean canWriteSettings(Context context) {\n        return !AndroidUtil.isMarshMallowOrLater() || Settings.System.canWrite(context);\n    }\n\n    public static boolean canReadStorage(Context context) {\n        return !AndroidUtil.isMarshMallowOrLater() || ContextCompat.checkSelfPermission(context,\n                Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;\n    }\n\n\n    private static void requestStoragePermission(Activity activity) {\n        ActivityCompat.requestPermissions(activity,\n                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},\n                PERMISSION_STORAGE_TAG);\n    }\n}\n"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/widget/BatteryLevelView.java",
    "content": "package com.jarvanmo.exoplayerview.widget;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.RectF;\nimport android.os.BatteryManager;\nimport androidx.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.view.View;\n\n/**\n * Created by mo on 17-4-19.\n * Copyright © 2017, cnyanglao, Co,. Ltd. All Rights Reserve\n */\n\npublic class BatteryLevelView extends View {\n\n    /**\n     * 画笔信息\n     */\n    private Paint mBatteryPaint;\n    private Paint mPowerPaint;\n    private float mBatteryStroke = 2f;\n    /**\n     * 屏幕高宽\n     */\n    private int measureWidth;\n    private int measureHeight;\n    /**\n     * 电池参数\n     */\n    private float mBatteryHeight = 30f; // 电池的高度\n    private float mBatteryWidth = 55f; // 电池的宽度\n    private float mCapHeight = 15f;\n    private float mCapWidth = 5f;\n    /**\n     * 电池电量\n     */\n    private float mPowerPadding = 1;\n    private float mPowerHeight = mBatteryHeight - mBatteryStroke\n            - mPowerPadding * 2; // 电池身体的高度\n    private float mPowerWidth = mBatteryWidth - mBatteryStroke - mPowerPadding\n            * 2;// 电池身体的总宽度\n    private float mPower = 10f;\n    /**\n     * 矩形\n     */\n    private RectF mBatteryRect;\n    private RectF mCapRect;\n    private RectF mPowerRect;\n\n\n    private boolean mIsCharging = false;\n\n    public BatteryLevelView(Context context) {\n        this(context, null);\n    }\n\n    public BatteryLevelView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public BatteryLevelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        initView();\n    }\n\n\n    public void initView() {\n\n        mBatteryPaint = new Paint();// 设置电池画笔\n        mBatteryPaint.setColor(Color.GRAY);\n        mBatteryPaint.setAntiAlias(true);\n        mBatteryPaint.setStyle(Paint.Style.STROKE);\n        mBatteryPaint.setStrokeWidth(mBatteryStroke);\n\n        mPowerPaint = new Paint();//设置电量画笔\n        mPowerPaint.setColor(getPowerColor());\n        mPowerPaint.setAntiAlias(true);\n        mPowerPaint.setStyle(Paint.Style.FILL);\n        mPowerPaint.setStrokeWidth(mBatteryStroke);\n\n        mBatteryRect = new RectF(mCapWidth, 0, mBatteryWidth, mBatteryHeight);//设置电池矩形\n\n        mCapRect = new RectF(0, (mBatteryHeight - mCapHeight) / 2, mCapWidth,\n                (mBatteryHeight - mCapHeight) / 2 + mCapHeight);//设置电池盖矩形\n\n        mPowerRect = new RectF(mCapWidth + mBatteryStroke / 2 + mPowerPadding\n                + mPowerWidth * ((100f - mPower) / 100f), // 需要调整左边的位置\n                mPowerPadding + mBatteryStroke / 2, // 需要考虑到 画笔的宽度\n                mBatteryWidth - mPowerPadding * 2, mBatteryStroke / 2\n                + mPowerPadding + mPowerHeight);// 设置电量矩形\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        canvas.save();\n        canvas.translate(measureWidth / 2, measureHeight / 2);\n        canvas.drawRoundRect(mBatteryRect, 2f, 2f, mBatteryPaint); // 画电池轮廓需要考虑 画笔的宽度\n        canvas.drawRoundRect(mCapRect, 2f, 2f, mBatteryPaint);// 画电池盖\n        canvas.drawRect(mPowerRect, mPowerPaint);// 画电量\n        canvas.restore();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        measureWidth = MeasureSpec.getSize(widthMeasureSpec);\n        measureHeight = MeasureSpec.getSize(heightMeasureSpec);\n        setMeasuredDimension(measureWidth, measureHeight);\n    }\n\n\n    public void setPower(float power) {\n        mPower = power;\n        if (mPower < 0) {\n            mPower = 0;\n        }\n        mPowerPaint.setColor(getPowerColor());\n\n        mPowerRect = new RectF(mCapWidth + mBatteryStroke / 2 + mPowerPadding\n                + mPowerWidth * ((100f - mPower) / 100f), // 需要调整左边的位置\n                mPowerPadding + mBatteryStroke / 2, // 需要考虑到 画笔的宽度\n                mBatteryWidth - mPowerPadding * 2, mBatteryStroke / 2\n                + mPowerPadding + mPowerHeight);\n        invalidate();\n    }\n\n\n    private int getPowerColor() {\n        if (mIsCharging) {\n            return Color.GREEN;\n        }\n\n        if (mPower <= 15) {\n            return Color.RED;\n        } else if (mPower <= 30) {\n            return Color.YELLOW;\n        } else {\n            return Color.WHITE;\n        }\n    }\n\n    private BroadcastReceiver mPowerConnectionReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);\n            mIsCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||\n                    status == BatteryManager.BATTERY_STATUS_FULL;\n\n            int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);\n            int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);\n\n            float percent = ((float) (level * 100)) / scale;\n            setPower(percent);\n        }\n    };\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        getContext().registerReceiver(mPowerConnectionReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        getContext().unregisterReceiver(mPowerConnectionReceiver);\n    }\n\n\n}"
  },
  {
    "path": "exoplayerview/src/main/java/com/jarvanmo/exoplayerview/widget/BatteryStatusView.java",
    "content": "package com.jarvanmo.exoplayerview.widget;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.BatteryManager;\nimport androidx.annotation.Nullable;\nimport androidx.appcompat.widget.AppCompatImageView;\nimport android.util.AttributeSet;\n\nimport com.jarvanmo.exoplayerview.R;\n\n/**\n * Created by mo on 17-4-19.\n * Copyright © 2017, cnyanglao, Co,. Ltd. All Rights Reserve\n */\n\npublic class BatteryStatusView extends AppCompatImageView {\n\n\n    public BatteryStatusView(Context context) {\n        this(context, null);\n    }\n\n    public BatteryStatusView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public BatteryStatusView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    private void init() {\n        setImageResource(R.drawable.stat_sys_battery);\n        setImageLevel(100);\n    }\n\n\n    private BroadcastReceiver mPowerConnectionReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);\n            boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||\n                    status == BatteryManager.BATTERY_STATUS_FULL;\n\n            int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);\n            int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);\n\n            if (isCharging) {\n                setImageResource(R.drawable.stat_sys_battery_charge);\n            } else {\n                setImageResource(R.drawable.stat_sys_battery);\n            }\n            setImageLevel(level);\n\n        }\n    };\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        getContext().registerReceiver(mPowerConnectionReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        getContext().unregisterReceiver(mPowerConnectionReceiver);\n    }\n\n\n}"
  },
  {
    "path": "exoplayerview/src/main/res/drawable/ic_arrow_back_white_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\" />\n</vector>\n"
  },
  {
    "path": "exoplayerview/src/main/res/drawable/ic_error_outline_white_48dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"48dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\"\n    android:width=\"48dp\">\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z\" />\n</vector>\n"
  },
  {
    "path": "exoplayerview/src/main/res/drawable/stat_sys_battery.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/* //device/apps/common/res/drawable/stat_sys_battery.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<level-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--<item-->\n        <!--android:maxLevel=\"4\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_0\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"15\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_15\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"35\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_28\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"49\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_43\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"60\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_57\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"75\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_71\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"90\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_85\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"100\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_100\" />-->\n</level-list>\n\n"
  },
  {
    "path": "exoplayerview/src/main/res/drawable/stat_sys_battery_charge.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<level-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--<item-->\n        <!--android:maxLevel=\"4\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim0\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"15\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim15\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"35\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim28\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"49\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim43\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"60\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim57\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"75\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim71\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"90\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim85\" />-->\n    <!--<item-->\n        <!--android:maxLevel=\"100\"-->\n        <!--android:drawable=\"@drawable/stat_sys_battery_charge_anim100\" />-->\n</level-list>\n\n\n"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_playback_controller_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/controllerWrapper\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/default_playback_background\"\n    android:orientation=\"horizontal\">\n\n    <ImageButton\n        android:id=\"@id/exo_player_play\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:contentDescription=\"@null\"\n        android:src=\"@drawable/ic_play_arrow_white_36dp\" />\n\n    <ImageButton\n        android:id=\"@id/exo_player_pause\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:contentDescription=\"@null\"\n        android:src=\"@drawable/ic_pause_white_36dp\" />\n\n    <TextView\n        android:id=\"@id/exo_player_position\"\n        android:layout_width=\"50dp\"\n        android:layout_height=\"match_parent\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center\"\n        android:lines=\"1\"\n        android:text=\"@string/default_video_time\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"@dimen/default_video_view_time_text_size\" />\n\n\n    <com.google.android.exoplayer2.ui.DefaultTimeBar\n        android:id=\"@id/exo_player_progress\"\n        style=\"?android:attr/seekBarStyle\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_weight=\"1\"\n        android:maxHeight=\"10dp\"\n        android:maxWidth=\"10dp\" />\n\n    <TextView\n        android:id=\"@id/exo_player_duration\"\n        android:layout_width=\"50dp\"\n        android:layout_height=\"match_parent\"\n        android:ellipsize=\"end\"\n        android:gravity=\"center\"\n        android:lines=\"1\"\n        android:text=\"@string/default_video_time\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"@dimen/default_video_view_time_text_size\" />\n\n    <ImageButton\n        android:id=\"@id/exo_player_enter_fullscreen\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:contentDescription=\"@null\"\n        android:src=\"@drawable/ic_fullscreen_white_36dp\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_playback_controller_bottom_landscape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"55dp\"\n    android:background=\"@color/default_playback_background\"\n    android:paddingBottom=\"5dp\"\n    android:paddingTop=\"0dp\">\n\n    <!--<ImageButton-->\n    <!--android:id=\"@id/exo_player_play_landscape\"-->\n    <!--android:layout_width=\"wrap_content\"-->\n    <!--android:layout_height=\"wrap_content\"-->\n    <!--android:layout_marginBottom=\"8dp\"-->\n    <!--android:layout_marginTop=\"8dp\"-->\n    <!--android:background=\"?android:attr/selectableItemBackground\"-->\n    <!--android:contentDescription=\"@string/exo_controls_play_description\"-->\n    <!--app:layout_constraintBottom_toBottomOf=\"parent\"-->\n    <!--app:layout_constraintEnd_toStartOf=\"@id/exo_player_time_landscape\"-->\n    <!--app:layout_constraintStart_toStartOf=\"parent\"-->\n    <!--app:layout_constraintTop_toTopOf=\"@+id/exo_player_progress_landscape\"-->\n    <!--app:srcCompat=\"@drawable/ic_play_arrow_white_36dp\" />-->\n\n    <FrameLayout\n        android:id=\"@id/exo_player_play_wrapper_landscape\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dp\"\n        android:layout_marginTop=\"8dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/exo_player_progress_landscape\"\n        app:srcCompat=\"@drawable/ic_play_arrow_white_36dp\">\n\n        <ImageButton\n            android:id=\"@id/exo_player_play_landscape\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackground\"\n            android:contentDescription=\"@string/exo_controls_play_description\"\n            android:scaleType=\"centerInside\"\n            app:srcCompat=\"@drawable/ic_play_arrow_white_36dp\" />\n\n        <ImageButton\n            android:id=\"@id/exo_player_pause_landscape\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackground\"\n            android:contentDescription=\"@string/exo_controls_pause_description\"\n            android:scaleType=\"centerInside\"\n            android:visibility=\"gone\"\n            app:srcCompat=\"@drawable/ic_pause_white_36dp\" />\n    </FrameLayout>\n\n\n    <ImageButton\n        android:id=\"@id/exo_player_next_landscape\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:contentDescription=\"@string/exo_controls_next_description\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/exo_player_play_wrapper_landscape\"\n        app:layout_constraintTop_toBottomOf=\"@id/exo_player_progress_landscape\"\n        app:srcCompat=\"@drawable/ic_skip_next_white_36dp\" />\n\n    <ImageButton\n        android:id=\"@id/exo_player_exit_fullscreen\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:contentDescription=\"@null\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/exo_player_play_wrapper_landscape\"\n        app:srcCompat=\"@drawable/ic_fullscreen_exit_white_36dp\" />\n\n    <FrameLayout\n        android:id=\"@id/exo_player_controller_bottom_custom_view_landscape\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"60dp\"\n        android:layout_marginBottom=\"2dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/exo_player_current_quality_landscape\"\n        app:layout_constraintStart_toEndOf=\"@+id/exo_player_position_duration_landscape\"\n        app:layout_constraintTop_toBottomOf=\"@+id/exo_player_progress_landscape\">\n\n    </FrameLayout>\n\n    <TextView\n        android:id=\"@id/exo_player_current_quality_landscape\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        android:gravity=\"center\"\n        android:paddingStart=\"5dp\"\n        android:paddingEnd=\"5dp\"\n        android:textColor=\"@android:color/white\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@+id/exo_player_exit_fullscreen\"\n        app:layout_constraintStart_toEndOf=\"@id/exo_player_controller_bottom_custom_view_landscape\"\n        app:layout_constraintTop_toTopOf=\"@+id/exo_player_position_duration_landscape\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:text=\"1080p\" />\n\n    <TextView\n        android:id=\"@id/exo_player_position_duration_landscape\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:gravity=\"center_vertical\"\n        android:padding=\"3dp\"\n        android:text=\"00:00\"\n        android:textColor=\"@android:color/white\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/exo_player_play_wrapper_landscape\"\n        app:layout_constraintEnd_toStartOf=\"@id/exo_player_controller_bottom_custom_view_landscape\"\n        app:layout_constraintHorizontal_bias=\"0.065\"\n        app:layout_constraintStart_toEndOf=\"@id/exo_player_next_landscape\"\n        app:layout_constraintTop_toTopOf=\"@+id/exo_player_play_wrapper_landscape\"\n        tools:text=\"00:04/06:09\" />\n\n    <com.google.android.exoplayer2.ui.DefaultTimeBar\n        android:id=\"@+id/exo_player_progress_landscape\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:max=\"10\"\n        android:progress=\"3\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_playback_controller_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:background=\"@color/default_playback_background\">\n\n    <ImageButton\n        android:id=\"@id/exo_player_controller_back\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:background=\"@null\"\n        android:contentDescription=\"back\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_arrow_back_white_24dp\" />\n\n    <TextView\n        android:id=\"@id/exo_player_video_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:gravity=\"center_vertical|start\"\n        android:maxLines=\"1\"\n        tools:text=\"正常播放\"\n        android:textColor=\"@android:color/white\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n        app:layout_constraintStart_toEndOf=\"@+id/exo_player_controller_back\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <FrameLayout\n        android:id=\"@+id/exo_player_controller_top_custom_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/guideline\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/guideline\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintGuide_percent=\"0.50\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_playback_controller_top_landscape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/default_playback_background\">\n\n    <ImageButton\n        android:id=\"@id/exo_player_controller_back_landscape\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:background=\"@null\"\n        android:contentDescription=\"back\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:srcCompat=\"@drawable/ic_arrow_back_white_24dp\" />\n\n    <TextView\n        android:id=\"@id/exo_player_video_name_landscape\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:gravity=\"center_vertical|start\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"0dp\"\n        android:paddingStart=\"8dp\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"18sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@+id/guideline\"\n        app:layout_constraintStart_toEndOf=\"@+id/exo_player_controller_back_landscape\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"正在播放\" />\n\n    <FrameLayout\n        android:id=\"@+id/exo_player_controller_top_custom_view_landscape\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@+id/guideline\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/guideline\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintGuide_percent=\"0.50\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_player_quality_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/containerWrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/default_playback_background\"\n        app:layout_constraintStart_toEndOf=\"@+id/guideline3\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@id/exo_player_quality_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginEnd=\"16dp\" />\n    </FrameLayout>\n\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/guideline3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintGuide_percent=\"0.85\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_video_playback_control_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n    <include\n        android:id=\"@id/exo_player_controller_top\"\n        layout=\"@layout/exo_playback_controller_top\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"50dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <include\n        android:id=\"@id/exo_player_controller_top_landscape\"\n        layout=\"@layout/exo_playback_controller_top_landscape\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"60dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <ProgressBar\n        android:id=\"@id/exo_player_loading\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n\n    <FrameLayout\n        android:id=\"@id/exo_player_center_info_wrapper\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:focusableInTouchMode=\"true\"\n        app:layout_constraintBottom_toTopOf=\"@id/exo_player_controller_bottom\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/exo_player_controller_top\">\n\n        <TextView\n            android:id=\"@id/exo_player_center_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\" />\n\n        <TextView\n            android:id=\"@id/exo_player_center_error\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:drawableTop=\"@drawable/ic_error_outline_white_48dp\"\n            android:textColor=\"@android:color/white\"\n            android:visibility=\"gone\" />\n    </FrameLayout>\n\n    <include\n        android:id=\"@id/exo_player_controller_bottom_landscape\"\n        layout=\"@layout/exo_playback_controller_bottom_landscape\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n\n    <include\n        android:id=\"@id/exo_player_controller_bottom\"\n        layout=\"@layout/exo_playback_controller_bottom\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n"
  },
  {
    "path": "exoplayerview/src/main/res/layout/exo_video_view.xml",
    "content": "<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <com.google.android.exoplayer2.ui.AspectRatioFrameLayout\n        android:id=\"@id/exo_player_content_frame\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:keepScreenOn=\"true\">\n\n        <!-- Video surface will be inserted as the first child of the content frame. -->\n\n        <View\n            android:id=\"@id/exo_player_shutter\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"@android:color/black\" />\n\n        <ImageView\n            android:id=\"@id/exo_player_artwork\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"fitXY\" />\n\n        <com.google.android.exoplayer2.ui.SubtitleView\n            android:id=\"@id/exo_player_subtitles\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n    </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>\n\n    <FrameLayout\n        android:id=\"@id/exo_player_overlay\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n    <View\n        android:id=\"@id/exo_player_controller_placeholder\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</merge>\n"
  },
  {
    "path": "exoplayerview/src/main/res/layout/item_quality.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:id=\"@+id/qualityDes\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:padding=\"10dp\"\n        android:maxLines=\"1\"\n        android:ellipsize=\"end\"\n        android:textSize=\"18sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        android:textColor=\"@android:color/white\"\n        tools:text=\"高清720p\" />\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "exoplayerview/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <attr name=\"showTimeout\" format=\"integer\" />\n    <attr name=\"rewindIncrement\" format=\"integer\" />\n    <attr name=\"fastForwardIncrement\" format=\"integer\" />\n    <attr name=\"topWrapperTextSize\" format=\"dimension\" />\n\n    <!-- Must be kept in sync with AspectRatioFrameLayout -->\n    <attr name=\"resize_mode\" format=\"enum\">\n        <enum name=\"fit\" value=\"0\" />\n        <enum name=\"fixed_width\" value=\"1\" />\n        <enum name=\"fixed_height\" value=\"2\" />\n        <enum name=\"fill\" value=\"3\" />\n    </attr>\n\n    <!-- Must be kept in sync with SimpleExoPlayerView -->\n    <attr name=\"surface_type\" format=\"enum\">\n        <enum name=\"none\" value=\"0\" />\n        <enum name=\"surface_view\" value=\"1\" />\n        <enum name=\"texture_view\" value=\"2\" />\n    </attr>\n    <attr name=\"show_timeout\" format=\"integer\" />\n    <attr name=\"rewind_increment\" format=\"integer\" />\n    <attr name=\"fastforward_increment\" format=\"integer\" />\n    <attr name=\"player_layout_id\" format=\"reference\" />\n    <attr name=\"controller_layout_id\" format=\"reference\" />\n    <attr name=\"repeat_toggle_modes\">\n        <flag name=\"none\" value=\"0\" />\n        <flag name=\"one\" value=\"1\" />\n        <flag name=\"all\" value=\"2\" />\n    </attr>\n    <attr name=\"show_shuffle_button\" format=\"boolean\" />\n    <attr name=\"enableGesture\" format=\"boolean\" />\n    <!--<enum getDisplayName=\"all\" value=\"0b1111\"/>-->\n    <!--<enum getDisplayName=\"top\" value=\"0b1000\"/>-->\n    <!--<enum getDisplayName=\"top_landscape\" value=\"0b0100\"/>-->\n    <!--<enum getDisplayName=\"bottom\" value=\"0b0010\"/>-->\n    <!--<enum getDisplayName=\"bottom_landscape\" value=\"0b0001\"/>-->\n    <attr name=\"controller_display_mode\">\n        <flag name=\"all\" value=\"15\" />\n        <flag name=\"top\" value=\"8\" />\n        <flag name=\"top_landscape\" value=\"4\" />\n        <flag name=\"bottom\" value=\"2\" />\n        <flag name=\"bottom_landscape\" value=\"1\" />\n        <flag name=\"none\" value=\"0\" />\n    </attr>\n\n    <attr name=\"controller_background\" format=\"reference\" />\n\n    <declare-styleable name=\"ExoVideoView\">\n        <attr name=\"use_artwork\" format=\"boolean\" />\n        <attr name=\"shutter_background_color\" format=\"color\" />\n        <attr name=\"default_artwork\" format=\"reference\" />\n        <attr name=\"use_controller\" format=\"boolean\" />\n        <attr name=\"hide_on_touch\" format=\"boolean\" />\n        <attr name=\"auto_show\" format=\"boolean\" />\n        <attr name=\"resize_mode\" />\n        <attr name=\"surface_type\" />\n        <attr name=\"show_timeout\" />\n        <attr name=\"rewind_increment\" />\n        <attr name=\"fastforward_increment\" />\n        <attr name=\"player_layout_id\" />\n        <attr name=\"controller_layout_id\" />\n\n        <attr name=\"controller_display_mode\" />\n        <attr name=\"controller_background\" />\n        <attr name=\"enable_multi_quality\" format=\"boolean\" />\n        <attr name=\"enableGesture\"/>\n    </declare-styleable>\n\n\n    <declare-styleable name=\"ExoVideoPlaybackControlView\">\n        <attr name=\"show_timeout\" />\n        <attr name=\"rewind_increment\" />\n        <attr name=\"fastforward_increment\" />\n        <attr name=\"repeat_toggle_modes\" />\n        <attr name=\"show_shuffle_button\" />\n        <attr name=\"controller_layout_id\" />\n        <attr name=\"controller_display_mode\" />\n        <attr name=\"controller_background\" />\n        <attr name=\"enableGesture\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"DefaultTimeBar\">\n        <attr name=\"bar_height\" format=\"dimension\" />\n        <attr name=\"touch_target_height\" format=\"dimension\" />\n        <attr name=\"ad_marker_width\" format=\"dimension\" />\n        <attr name=\"scrubber_enabled_size\" format=\"dimension\" />\n        <attr name=\"scrubber_disabled_size\" format=\"dimension\" />\n        <attr name=\"scrubber_dragged_size\" format=\"dimension\" />\n        <attr name=\"scrubber_drawable\" format=\"reference\" />\n        <attr name=\"played_color\" format=\"color\" />\n        <attr name=\"scrubber_color\" format=\"color\" />\n        <attr name=\"buffered_color\" format=\"color\" />\n        <attr name=\"unplayed_color\" format=\"color\" />\n        <attr name=\"ad_marker_color\" format=\"color\" />\n        <attr name=\"played_ad_marker_color\" format=\"color\" />\n    </declare-styleable>\n\n\n</resources>"
  },
  {
    "path": "exoplayerview/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"default_playback_background\">#3D000000</color>\n\n    <color name=\"batterymeter_frame_color\">#4DFFFFFF</color><!-- 30% white -->\n    <color name=\"batterymeter_charge_color\">#FFFFFFFF</color>\n    <color name=\"batterymeter_bolt_color\">#FFFFFFFF</color>\n\n    <color name=\"dark_mode_icon_color_single_tone\">#99000000</color>\n    <color name=\"dark_mode_icon_color_dual_tone_background\">#3d000000</color>\n    <color name=\"dark_mode_icon_color_dual_tone_fill\">#7a000000</color>\n\n    <color name=\"light_mode_icon_color_single_tone\">#ffffff</color>\n    <color name=\"light_mode_icon_color_dual_tone_background\">#4dffffff</color>\n    <color name=\"light_mode_icon_color_dual_tone_fill\">#ffffff</color>\n</resources>"
  },
  {
    "path": "exoplayerview/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"common_zero_dp\">0dp</dimen>\n    <dimen name=\"default_video_view_time_text_size\">15sp</dimen>\n    <dimen name=\"video_view_controller_margin\">10dp</dimen>\n    <dimen name=\"video_view_center_info_txt_size\">18sp</dimen>\n</resources>"
  },
  {
    "path": "exoplayerview/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"exo_player_play\" type=\"id\" />\n    <item name=\"exo_player_position\" type=\"id\" />\n    <item name=\"exo_player_progress\" type=\"id\" />\n    <item name=\"exo_player_duration\" type=\"id\" />\n    <item name=\"exo_player_enter_fullscreen\" type=\"id\" />\n    <item name=\"exo_player_play_landscape\" type=\"id\" />\n    <item name=\"exo_player_pause_landscape\" type=\"id\" />\n    <item name=\"exo_player_time_landscape\" type=\"id\" />\n    <item name=\"exo_player_next_landscape\" type=\"id\" />\n    <item name=\"exo_player_progress_landscape\" type=\"id\" />\n    <item name=\"exo_player_exit_fullscreen\" type=\"id\" />\n    <item name=\"exo_player_controller_bottom_custom_view_landscape\" type=\"id\" />\n    <item name=\"exo_player_position_duration_landscape\" type=\"id\" />\n    <item name=\"exo_player_video_name\" type=\"id\" />\n    <item name=\"exo_player_video_name_landscape\" type=\"id\" />\n    <item name=\"exo_player_battery_landscape\" type=\"id\" />\n    <item name=\"exo_player_controller_top\" type=\"id\" />\n    <item name=\"exo_player_controller_top_landscape\" type=\"id\" />\n    <item name=\"exo_player_loading\" type=\"id\" />\n\n    <item name=\"exo_player_center_info_wrapper\" type=\"id\" />\n    <item name=\"exo_player_center_text\" type=\"id\" />\n    <item name=\"exo_player_controller_bottom_landscape\" type=\"id\" />\n    <item name=\"exo_player_controller_bottom\" type=\"id\" />\n    <item name=\"exo_player_content_frame\" type=\"id\" />\n    <item name=\"exo_player_shutter\" type=\"id\" />\n    <item name=\"exo_player_artwork\" type=\"id\" />\n    <item name=\"exo_player_overlay\" type=\"id\" />\n    <item name=\"exo_player_subtitles\" type=\"id\" />\n    <item name=\"exo_player_controller_placeholder\" type=\"id\" />\n    <item name=\"exo_player_controller\" type=\"id\" />\n    <item name=\"exo_player_pause\" type=\"id\" />\n    <item name=\"exo_player_play_wrapper_landscape\" type=\"id\" />\n    <item name=\"exo_player_current_quality_landscape\" type=\"id\" />\n    <item name=\"exo_player_quality_container\" type=\"id\" />\n    <item name=\"exo_player_quality_selector\" type=\"id\" />\n\n    <item name=\"exo_player_controller_top_custom_view\" type=\"id\" />\n    <item name=\"exo_player_controller_top_custom_view_landscape\" type=\"id\" />\n    <item name=\"exo_player_center_error\" type=\"id\" />\n\n    <item name=\"exo_player_controller_back\" type=\"id\"/>\n    <item name=\"exo_player_controller_back_landscape\" type=\"id\"/>\n</resources>"
  },
  {
    "path": "exoplayerview/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">ExoPlayerView</string>\n    <string name=\"default_video_time\" translatable=\"false\">00:00</string>\n    <string name=\"default_video_combined_time\" translatable=\"false\">00:00/00:00</string>\n    <string name=\"time_am\">AM</string>\n    <string name=\"time_pm\">PM</string>\n    <string name=\"battery_percent\">battery:%1$d%%</string>\n    <string name=\"volume_changing\">volume:%1$d%%</string>\n    <string name=\"brightness_changing\">brightness:%1$d%%</string>\n    <string name=\"player_error\">player error:%1$d</string>\n</resources>\n"
  },
  {
    "path": "exoplayerview/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">ExoPlayerView</string>\n    <string name=\"time_am\">上午</string>\n    <string name=\"time_pm\">下午</string>\n    <string name=\"battery_percent\">电量:%1$d%%</string>\n    <string name=\"volume_changing\">音量:%1$d%%</string>\n    <string name=\"brightness_changing\">亮度:%1$d%%</string>\n    <string name=\"player_error\">播放错误:%1$d</string>\n</resources>\n"
  },
  {
    "path": "exoplayerview/src/test/java/com/jarvanmo/exoplayerview/ExampleUnitTest.java",
    "content": "package com.jarvanmo.exoplayerview;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Thu Jul 23 09:49:01 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.1.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\nandroid.enableJetifier=true\nandroid.useAndroidX=true\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':demo'\ninclude ':exoplayerview'"
  }
]