[
  {
    "path": ".gitignore",
    "content": "# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ngen/\nout/\nbuild.log\nbuild-log.xml\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n.project\n.classpath\n.settings\ntarget/\nbuild/\nclasses/\ngen-external-apklibs/\ntmp/\n\n# IDEA Ignores\n*.iml\n*.ipr\n*.iws\n.idea/\n.gradle/\n\n# Android\nlocal.properties\n\n# MAC\n*.DS_Store\n\n# api scripts\nsession.key\naccess.token\nresponse.txt\n.pt\n*.orig\nterminator/\n\n=======\n\napp/build\napp-test/build\nlint-results.xml\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\n\nandroid:\n  components:\n    - build-tools-23.0.1\n    - android-23\n    - extra-android-support\n    - extra-google-m2repository\n    - extra-android-m2repository\n\nscript:\n  - ./gradlew clean build\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "     Copyright 2014 David Gonzalez\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\t\t\t\t                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"
  },
  {
    "path": "README.md",
    "content": "Fenster\n=============================\n\nA library to display videos in a `TextureView` using a custom `MediaPlayer` controller as described in this blog post http://www.malmstein.com/blog/2014/08/09/how-to-use-a-textureview-to-display-a-video-with-custom-media-player-controls/\n\n![Demo gif](https://raw.githubusercontent.com/malmstein/Fenster/master/art/video_example.gif)\n\nInstall\n=============================\n\n[ ![Download](https://api.bintray.com/packages/malmstein/maven/fenster/images/download.svg) ](https://bintray.com/malmstein/maven/fenster/_latestVersion)\n\nTo get the current snapshot version:\n\n```groovy\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.malmstein:fenster:0.0.2'\n    }\n}\n```\n\n### minSDK\n\nThe minSDK for the use of the library is minSDK 16\n\nDisplaying a video with custom controller\n=============================\n\n### Add a TextureVideoView and a PlayerController to your Activity or Fragment\n\n```xml\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\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_bg\"\n  tools:context=\".DemoActivity\">\n\n  <com.malmstein.fenster.view.FensterVideoView\n    android:id=\"@+id/play_video_texture\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:keepScreenOn=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n  <com.malmstein.fenster.controller.MediaFensterPlayerController\n    android:id=\"@+id/play_video_controller\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\"\n    android:animateLayoutChanges=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n</FrameLayout>\n```\n\n### Setting video URL\n\nIn order to display a video, simply set the video URL and call start. **You can also start the video from a desired second too**.\n\n\n```java\n@Override\nprotected void onPostCreate(Bundle savedInstanceState) {\n    super.onPostCreate(savedInstanceState);\n\n    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);\n    playerController = (PlayerController) findViewById(R.id.play_video_controller);\n\n    textureView.setMediaController(playerController);\n\n    textureView.setVideo(\"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\",\n                PlayerController.DEFAULT_VIDEO_START);\n    textureView.start();\n}\n```\n\n### Exposed listeners\n\nBy default there are the exposed listeners. The `NavigationListener` will listen to the to **Previous** and **Next** events triggered\nby the controller. The `VisibilityListener` will be triggered when the `PlayerController` visibility changes.\n\n```java\nplayerController.setNavigationListener(this);\nplayerController.setVisibilityListener(this);\n```\n\nUsing the Gesture Detection Player Controller\n=============================\n\n### Attach a listener to your player controller\n\nAs described in this blog post http://www.malmstein.com/how-to-use-a-textureview-to-display-a-video-with-custom-media-player-controls/\nit's very simple to use. Just add a listener to Player Controller\n\n```java\nplayerController.setFensterEventsListener(this);\n```\n\nThe Fenster Events Listener allows you to react to the gestures\n\n```java\npublic interface FensterEventsListener {\n\n    void onTap();\n\n    void onHorizontalScroll(MotionEvent event, float delta);\n\n    void onVerticalScroll(MotionEvent event, float delta);\n\n    void onSwipeRight();\n\n    void onSwipeLeft();\n\n    void onSwipeBottom();\n\n    void onSwipeTop();\n}\n```\n\n### Use MediaPlayerController instead of SimpleMediaPlayerController\n\nMediaFensterPlayerController also shows volume and brightness controls, if you just want to use a simple media controller\nthen the recommendation is to use SimpleMediaFensterPlayerController\n\n```xml\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\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_bg\"\n  tools:context=\".DemoActivity\">\n\n  <com.malmstein.fenster.view.FensterVideoView\n    android:id=\"@+id/play_video_texture\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:keepScreenOn=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n  <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController\n    android:id=\"@+id/play_video_controller\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\"\n    android:animateLayoutChanges=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n</FrameLayout>\n```\n\n### Using the Volume and Brightness Seekbar\n\nAdd them to your layout:\n\n```xml\n  <com.malmstein.fenster.seekbar.BrightnessSeekBar\n    android:id=\"@+id/media_controller_volume\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\" />\n```\n\n```xml\n  <com.malmstein.fenster.seekbar.VolumeSeekBar\n    android:id=\"@+id/media_controller_volume\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\" />\n```\n\nInitialise them from your Fragment or Activity:\n\n```java\nmVolume = (VolumeSeekBar) findViewById(R.id.media_controller_volume);\nmVolume.initialise(this);\n\nmBrightness = (BrightnessSeekBar) findViewById(R.id.media_controller_brightness);\nmBrightness.initialise(this);\n\n```\n\nYou'll get a callback when the seekbar is being dragged:\n\n```java\n@Override\npublic void onVolumeStartedDragging() {\n    mDragging = true;\n}\n\n@Override\npublic void onVolumeFinishedDragging() {\n    mDragging = false;\n}\n\n@Override\npublic void onBrigthnessStartedDragging() {\n    mDragging = true;\n}\n\n@Override\npublic void onBrightnessFinishedDragging() {\n    mDragging = false;\n}\n```\n\nSupport for different video origins\n=============================\n\nThe `setVideo()` method allows you to load remote or local video files. You can also set the start time of the video \n(useful if you want to resume content), passing in a integer which corresponds to Milliseconds.\n\n### Loading a remote stream\n\n\n```java\n@Override\nprotected void onPostCreate(Bundle savedInstanceState) {\n    super.onPostCreate(savedInstanceState);\n\n    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);\n    playerController = (PlayerController) findViewById(R.id.play_video_controller);\n\n    textureView.setMediaController(playerController);\n\n    textureView.setVideo(\"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\");\n    textureView.start();\n}\n```\n\n### Loading a local stream\n\n`Fenster` uses the [AssetFileDescriptor](http://developer.android.com/intl/es/reference/android/content/res/AssetFileDescriptor.html) in \norder to load a local video stream.\n\n\n```java\n@Override\nprotected void onPostCreate(Bundle savedInstanceState) {\n    super.onPostCreate(savedInstanceState);\n\n    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);\n    playerController = (PlayerController) findViewById(R.id.play_video_controller);\n\n    textureView.setMediaController(playerController);\n    \n    AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.raw.big_buck_bunny);\n    textureView.setVideo(assetFileDescriptor);\n   \n    textureView.start();\n}\n```\n\n\nSupport for video scaling modes\n=============================\n\nSets video scaling mode. To make the target video scaling mode effective during playback, \nthe default video scaling mode is VIDEO_SCALING_MODE_SCALE_TO_FIT. Uses [setVideoScalingMode](http://developer.android.com/intl/es/reference/android/media/MediaPlayer.html)\n\nThere are two different video scaling modes: `scaleToFit` and `crop`\n\nIn order to use it, `Fenster` allows you to pass in an argument from the xml layout:\n\n```xml\n<com.malmstein.fenster.view.FensterVideoView\n  android:id=\"@+id/play_video_texture\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:scaleType=\"crop\" />\n```\n\n  \nLicense\n-------\n\n    (c) Copyright 2016 David Gonzalez\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": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.3.0'\n    }\n}\n\nrepositories {\n    jcenter()\n}\n\napply plugin: 'com.android.application'\n\ndependencies {\n    compile project(\":library\")\n//    compile 'com.malmstein:fenster:0.0.1'\n}\n\nandroid {\n    compileSdkVersion 20\n    buildToolsVersion \"20.0.0\"\n\n    defaultConfig {\n        applicationId \"com.malmstein.fenster.demo\"\n        minSdkVersion 16\n        targetSdkVersion 19\n        versionCode 1\n        versionName \"1.0\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n"
  },
  {
    "path": "app/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 /Applications/Android Studio.app/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 name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java",
    "content": "package com.malmstein.fenster;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/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.malmstein.fenster.demo\">\n\n  <uses-permission android:name=\"android.permission.INTERNET\" />\n  <uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />\n\n  <application\n    android:allowBackup=\"true\"\n    android:icon=\"@drawable/ic_launcher\"\n    android:label=\"@string/app_name\"\n    android:theme=\"@style/FensterTheme\">\n\n    <activity\n      android:name=\".MediaPlayerDemoActivity\"\n      android:label=\"@string/app_name\">\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\n    <activity\n      android:name=\".SimpleMediaPlayerActivity\"\n      android:configChanges=\"orientation|keyboardHidden|screenSize\"\n      android:label=\"@string/app_name\"\n      android:screenOrientation=\"sensorLandscape\"\n      android:theme=\"@style/FensterFullscreenTheme\" />\n\n    <activity\n      android:name=\".ScaleMediaPlayerActivity\"\n      android:configChanges=\"orientation|keyboardHidden|screenSize\"\n      android:label=\"@string/app_name\"\n      android:screenOrientation=\"sensorLandscape\" />\n\n    <activity\n      android:name=\".GestureMediaPlayerActivity\"\n      android:label=\"@string/app_name\"\n      android:screenOrientation=\"sensorLandscape\" />\n\n  </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/GestureMediaPlayerActivity.java",
    "content": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;\nimport com.malmstein.fenster.play.FensterVideoFragment;\n\npublic class GestureMediaPlayerActivity extends Activity implements FensterPlayerControllerVisibilityListener {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_gesture_media_player);\n    }\n\n    @Override\n    protected void onPostCreate(Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n        initVideo();\n    }\n\n    private void initVideo(){\n        findVideoFragment().setVisibilityListener(this);\n        findVideoFragment().playExampleVideo();\n    }\n\n    private FensterVideoFragment findVideoFragment(){\n        return (FensterVideoFragment) getFragmentManager().findFragmentById(R.id.play_demo_fragment);\n    }\n\n    @Override\n    public void onControlsVisibilityChange(boolean value) {\n        setSystemUiVisibility(value);\n    }\n\n    private void setSystemUiVisibility(final boolean visible) {\n        int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;\n\n        if (!visible) {\n            newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE\n                    | View.SYSTEM_UI_FLAG_FULLSCREEN\n                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;\n        }\n\n        final View decorView = getWindow().getDecorView();\n        decorView.setSystemUiVisibility(newVis);\n        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {\n            @Override\n            public void onSystemUiVisibilityChange(final int visibility) {\n                if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {\n                    findVideoFragment().showFensterController();\n                }\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/MediaPlayerDemoActivity.java",
    "content": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\n\npublic class MediaPlayerDemoActivity extends Activity implements View.OnClickListener {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_demo);\n        bindViews();\n    }\n\n    private void bindViews() {\n        findViewById(R.id.start_gesture_media_player_button).setOnClickListener(this);\n        findViewById(R.id.start_simple_media_player_button).setOnClickListener(this);\n        findViewById(R.id.local_file_media_player_button).setOnClickListener(this);\n        findViewById(R.id.start_scale_media_player_button).setOnClickListener(this);\n    }\n\n    @Override\n    public void onClick(View v) {\n        switch (v.getId()) {\n            case R.id.start_gesture_media_player_button:\n                startActivity(new Intent(this, GestureMediaPlayerActivity.class));\n                break;\n            case R.id.start_simple_media_player_button:\n                startActivity(new Intent(this, SimpleMediaPlayerActivity.class));\n                break;\n            case R.id.local_file_media_player_button:\n                Intent localStream = new Intent(this, SimpleMediaPlayerActivity.class);\n                localStream.putExtra(SimpleMediaPlayerActivity.KEY_LOCAL_FILE, true);\n                startActivity(localStream);\n                break;\n            case R.id.start_scale_media_player_button:\n                startActivity(new Intent(this, ScaleMediaPlayerActivity.class));\n                break;\n        }\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.demo_menu, menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        int id = item.getItemId();\n        if (id == R.id.action_about) {\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/ScaleMediaPlayerActivity.java",
    "content": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;\nimport com.malmstein.fenster.view.FensterVideoView;\n\npublic class ScaleMediaPlayerActivity extends Activity {\n\n    private FensterVideoView textureView;\n    private SimpleMediaFensterPlayerController fullScreenMediaPlayerController;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_scale_media_player);\n\n        bindViews();\n        initVideo();\n    }\n\n    @Override\n    protected void onPostCreate(Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n        textureView.setVideo(\"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\");\n        textureView.start();\n    }\n\n    private void bindViews() {\n        textureView = (FensterVideoView) findViewById(R.id.play_video_texture);\n        fullScreenMediaPlayerController = (SimpleMediaFensterPlayerController) findViewById(R.id.play_video_controller);\n    }\n\n    private void initVideo() {\n        textureView.setMediaController(fullScreenMediaPlayerController);\n        textureView.setOnPlayStateListener(fullScreenMediaPlayerController);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/SimpleMediaPlayerActivity.java",
    "content": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.content.res.AssetFileDescriptor;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;\nimport com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;\nimport com.malmstein.fenster.view.FensterVideoView;\n\npublic class SimpleMediaPlayerActivity extends Activity implements FensterPlayerControllerVisibilityListener {\n\n    public static final String KEY_LOCAL_FILE = BuildConfig.APPLICATION_ID + \"KEY_LOCAL_FILE\";\n    private FensterVideoView textureView;\n    private SimpleMediaFensterPlayerController fullScreenMediaPlayerController;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_simple_media_player);\n\n        bindViews();\n        initVideo();\n    }\n\n    @Override\n    protected void onPostCreate(Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n\n        if (getIntent().hasExtra(KEY_LOCAL_FILE)) {\n            AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.raw.big_buck_bunny);\n            textureView.setVideo(assetFileDescriptor);\n        } else {\n            textureView.setVideo(\"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\");\n        }\n\n        textureView.start();\n    }\n\n    private void bindViews() {\n        textureView = (FensterVideoView) findViewById(R.id.play_video_texture);\n        fullScreenMediaPlayerController = (SimpleMediaFensterPlayerController) findViewById(R.id.play_video_controller);\n    }\n\n    private void initVideo() {\n        fullScreenMediaPlayerController.setVisibilityListener(this);\n        textureView.setMediaController(fullScreenMediaPlayerController);\n        textureView.setOnPlayStateListener(fullScreenMediaPlayerController);\n    }\n\n    private void setSystemUiVisibility(final boolean visible) {\n        int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;\n\n        if (!visible) {\n            newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE\n                    | View.SYSTEM_UI_FLAG_FULLSCREEN\n                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;\n        }\n\n        final View decorView = getWindow().getDecorView();\n        decorView.setSystemUiVisibility(newVis);\n        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {\n            @Override\n            public void onSystemUiVisibilityChange(final int visibility) {\n                if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {\n                    fullScreenMediaPlayerController.show();\n                }\n            }\n        });\n    }\n\n    @Override\n    public void onControlsVisibilityChange(final boolean value) {\n        setSystemUiVisibility(value);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/layout/activity_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:orientation=\"vertical\"\n  android:padding=\"@dimen/activity_horizontal_margin\">\n\n  <TextView\n    android:id=\"@+id/demo_activity_controller_description\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:text=\"@string/description_demo_activity_controller\" />\n\n  <Button\n    android:id=\"@+id/start_simple_media_player_button\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n    android:text=\"@string/title_simple_media_player\" />\n\n  <Button\n    android:id=\"@+id/start_scale_media_player_button\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/title_scale_media_player\" />\n\n  <Button\n    android:id=\"@+id/start_gesture_media_player_button\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/title_gesture_media_player\" />\n\n  <TextView\n    android:id=\"@+id/demo_activity_origin_description\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n    android:gravity=\"center\"\n    android:text=\"@string/description_demo_activity_origin\" />\n\n  <Button\n    android:id=\"@+id/local_file_media_player_button\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginTop=\"@dimen/activity_vertical_margin\"\n    android:text=\"@string/title_local_media_player\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_gesture_media_player.xml",
    "content": "<fragment xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:id=\"@+id/play_demo_fragment\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  class=\"com.malmstein.fenster.play.FensterVideoFragment\"/>\n\n"
  },
  {
    "path": "app/src/main/res/layout/activity_scale_media_player.xml",
    "content": "<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:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@color/default_bg\"\n  android:gravity=\"center\"\n  tools:context=\".SimpleMediaPlayerActivity\">\n\n  <RelativeLayout\n    android:layout_width=\"300dp\"\n    android:layout_height=\"300dp\">\n\n    <com.malmstein.fenster.view.FensterVideoView\n      android:id=\"@+id/play_video_texture\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:scaleType=\"crop\" />\n\n    <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController\n      android:id=\"@+id/play_video_controller\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignParentBottom=\"true\"\n      android:animateLayoutChanges=\"true\" />\n\n  </RelativeLayout>\n\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_simple_media_player.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\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_bg\"\n  tools:context=\".SimpleMediaPlayerActivity\">\n\n  <com.malmstein.fenster.view.FensterVideoView\n    android:id=\"@+id/play_video_texture\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:keepScreenOn=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n  <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController\n    android:id=\"@+id/play_video_controller\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\"\n    android:animateLayoutChanges=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/demo_menu.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  tools:context=\"com.malmstein.fenster.demo.MediaPlayerDemoActivity\">\n  <item\n    android:id=\"@+id/action_about\"\n    android:title=\"@string/action_about\"\n    android:orderInCategory=\"100\"\n    android:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<resources>\n\n  <declare-styleable name=\"ButtonBarContainerTheme\">\n    <attr name=\"metaButtonBarStyle\" format=\"reference\" />\n    <attr name=\"metaButtonBarButtonStyle\" format=\"reference\" />\n  </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<resources>\n\n    <color name=\"black_overlay\">#66000000</color>\n    <color name=\"default_bg\">#aa000000</color>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n  <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n  <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <string name=\"app_name\">Fenster</string>\n  <string name=\"description_demo_activity_controller\">This demo shows two different MediaPlayer controllers. The Simple Media Controller demo displays a video in a TextureView with a customised Media Player Controller. The second demo extends de first one, allowing to use gestures in order to control the Media Player. </string>\n  <string name=\"description_demo_activity_origin\">Alternatively, you can also load a file from the Raw folder</string>\n\n  <string name=\"action_about\">About</string>\n\n  <string name=\"title_simple_media_player\">Simple Media Player Controller</string>\n  <string name=\"title_scale_media_player\">Different Media Player Scale Type</string>\n  <string name=\"title_gesture_media_player\">Gesture Media Player Controller</string>\n  <string name=\"title_local_media_player\">Load file from local Raw folder using an AssetFileDescriptor</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\n  <style name=\"FensterTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n\n  </style>\n\n  <style name=\"FensterFullscreenTheme\" parent=\"android:Theme.Holo\">\n    <item name=\"android:actionBarStyle\">@style/FensterFullscreenActionBarStyle</item>\n    <item name=\"android:windowActionBarOverlay\">true</item>\n    <item name=\"android:windowBackground\">@null</item>\n    <item name=\"metaButtonBarStyle\">?android:attr/buttonBarStyle</item>\n    <item name=\"metaButtonBarButtonStyle\">?android:attr/buttonBarButtonStyle</item>\n  </style>\n\n  <style name=\"FensterFullscreenActionBarStyle\" parent=\"android:Widget.Holo.ActionBar\">\n    <item name=\"android:background\">@color/default_bg</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n  <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.3.0'\n    }\n\n}\n\nallprojects {\n    buildscript {\n        repositories {\n            jcenter()\n        }\n    }\n\n    repositories {\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Aug 28 15:24:47 BST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.4-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any Gradle settings\n# configured through the IDE.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\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"
  },
  {
    "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# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\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\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\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\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": "library/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'com.novoda.bintray-release'\n\ndef gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()\ndef buildTime = new Date().format(\"yyyy-MM-dd'T'HH:mm'Z'\", TimeZone.getTimeZone('UTC'))\n\n// Manifest version information!\nint versionMajor = 0\nint versionMinor = 0\nint versionPatch = 2\nint versionBuild = 1 // bump for dogfood builds, public betas, etc.\n\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:1.3.0'\n        classpath 'com.novoda:bintray-release:0.3.4'\n    }\n}\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion \"23.0.2\"\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 23\n        versionCode versionMajor * 100**3 + versionMinor * 100**2 + versionPatch * 100 + versionBuild\n        versionName \"${versionMajor}.${versionMinor}.${versionPatch}\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n    lintOptions {\n        abortOnError false\n    }\n\n    resourcePrefix 'fen__'\n}\n\npublish {\n    userOrg = 'malmstein'\n    groupId = 'com.malmstein'\n    artifactId = 'fenster'\n    publishVersion = '0.0.2'\n    desc = 'A library to display videos in a TextureView using a custom MediaPlayer controller'\n    website = 'https://github.com/malmstein/fenster'\n}\n"
  },
  {
    "path": "library/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 /Applications/Android Studio.app/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 name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "library/publish.gradle",
    "content": "ext {\n    PUBLISH_GROUP_ID = 'com.malmstein'\n    PUBLISH_ARTIFACT_ID = 'fenster'\n    PUBLISH_VERSION = project.version\n\n    UPLOAD_NAME = PUBLISH_ARTIFACT_ID\n    UPLOAD_DESCRIPTION = 'A library to display videos in a TextureView using a custom MediaPlayer controller'\n    UPLOAD_WEBSITE = 'https://github.com/malmstein/fenster'\n    UPLOAD_ISSUE_TRACKER = \"${UPLOAD_WEBSITE}/issues\"\n    UPLOAD_REPOSITORY = \"${UPLOAD_WEBSITE}.git\"\n    UPLOAD_VERSION = PUBLISH_VERSION\n}\n\napply from: 'team-props/bintray.gradle'"
  },
  {
    "path": "library/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java",
    "content": "package com.malmstein.fenster;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  package=\"com.malmstein.fenster\">\n\n\n</manifest>\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/FensterPlayerController.java",
    "content": "package com.malmstein.fenster.controller;\n\nimport com.malmstein.fenster.play.FensterPlayer;\n\npublic interface FensterPlayerController {\n\n    void setMediaPlayer(FensterPlayer fensterPlayer);\n\n    void setEnabled(boolean value);\n\n    void show(int timeInMilliSeconds);\n\n    void show();\n\n    void hide();\n\n    void setVisibilityListener(FensterPlayerControllerVisibilityListener visibilityListener);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/FensterPlayerControllerVisibilityListener.java",
    "content": "package com.malmstein.fenster.controller;\n\n/**\n * Called to notify that the control have been made visible or hidden.\n * Implementation might want to show/hide actionbar or do other ui adjustments.\n * <p/>\n * Implementation must be provided via the corresponding setter.\n */\npublic interface FensterPlayerControllerVisibilityListener {\n\n    void onControlsVisibilityChange(boolean value);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/MediaFensterPlayerController.java",
    "content": "package com.malmstein.fenster.controller;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.ImageButton;\nimport android.widget.RelativeLayout;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.malmstein.fenster.R;\nimport com.malmstein.fenster.gestures.FensterEventsListener;\nimport com.malmstein.fenster.gestures.FensterGestureControllerView;\nimport com.malmstein.fenster.play.FensterPlayer;\nimport com.malmstein.fenster.seekbar.BrightnessSeekBar;\nimport com.malmstein.fenster.seekbar.VolumeSeekBar;\n\nimport java.util.Formatter;\nimport java.util.Locale;\n\n/**\n * Controller to manage syncing the ui models with the UI Controls and MediaPlayer.\n * <p/>\n * Note that the ui models have a narrow scope (i.e. chapter list, piece navigation),\n * their interaction is orchestrated by this controller.ø\n * <p/>\n * It's actually a view currently, as is the android MediaController.\n * (which is a bit odd and should be subject to change.)\n */\npublic final class MediaFensterPlayerController extends RelativeLayout implements FensterPlayerController, FensterEventsListener, VolumeSeekBar.Listener, BrightnessSeekBar.Listener {\n\n    /**\n     * Called to notify that the control have been made visible or hidden.\n     * Implementation might want to show/hide actionbar or do other ui adjustments.\n     * <p/>\n     * Implementation must be provided via the corresponding setter.\n     */\n\n    public static final String TAG = \"PlayerController\";\n\n    public static final int DEFAULT_VIDEO_START = 0;\n    public static final int ONE_FINGER = 1;\n    public static final int MAX_VIDEO_PROGRESS = 1000;\n    public static final int SKIP_VIDEO_PROGRESS = MAX_VIDEO_PROGRESS / 10;\n    private static final int DEFAULT_TIMEOUT = 5000;\n    private final OnClickListener mPauseListener = new OnClickListener() {\n        public void onClick(final View v) {\n            doPauseResume();\n            show(DEFAULT_TIMEOUT);\n        }\n    };\n    private static final int FADE_OUT = 1;\n    private static final int SHOW_PROGRESS = 2;\n    private FensterPlayerControllerVisibilityListener visibilityListener;\n    private FensterPlayer mFensterPlayer;\n    private boolean mShowing;\n    private boolean mDragging;\n    private final Handler mHandler = new Handler() {\n        @Override\n        public void handleMessage(final Message msg) {\n            int pos;\n            switch (msg.what) {\n                case FADE_OUT:\n                    if (mFensterPlayer.isPlaying()) {\n                        hide();\n                    } else {\n                        // re-schedule to check again\n                        Message fadeMessage = obtainMessage(FADE_OUT);\n                        removeMessages(FADE_OUT);\n                        sendMessageDelayed(fadeMessage, DEFAULT_TIMEOUT);\n                    }\n                    break;\n                case SHOW_PROGRESS:\n                    pos = setProgress();\n                    if (!mDragging && mShowing && mFensterPlayer.isPlaying()) {\n                        final Message message = obtainMessage(SHOW_PROGRESS);\n                        sendMessageDelayed(message, 1000 - (pos % 1000));\n                    }\n                    break;\n            }\n        }\n    };\n    private boolean mManualDragging;\n    private boolean mFirstTimeLoading = true;\n    private StringBuilder mFormatBuilder;\n    private Formatter mFormatter;\n    private FensterGestureControllerView gestureControllerView;\n    private View bottomControlsArea;\n    private SeekBar mProgress;\n    private BrightnessSeekBar mBrightness;\n    private VolumeSeekBar mVolume;\n    private TextView mEndTime;\n    private TextView mCurrentTime;\n    // There are two scenarios that can trigger the seekbar listener to trigger:\n    //\n    // The first is the user using the touchpad to adjust the posititon of the\n    // seekbar's thumb. In this case onStartTrackingTouch is called followed by\n    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.\n    // We're setting the field \"mDragging\" to true for the duration of the dragging\n    // session to avoid jumps in the position in case of ongoing playback.\n    //\n    // The second scenario involves the user operating the scroll ball, in this\n    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,\n    // we will simply apply the updated position without suspending regular updates.\n    private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {\n        public void onStartTrackingTouch(final SeekBar bar) {\n            show(3600000);\n\n            mDragging = true;\n\n            // By removing these pending progress messages we make sure\n            // that a) we won't update the progress while the user adjusts\n            // the seekbar and b) once the user is done dragging the thumb\n            // we will post one of these messages to the queue again and\n            // this ensures that there will be exactly one message queued up.\n            mHandler.removeMessages(SHOW_PROGRESS);\n        }\n\n        public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {\n            if (!fromuser && !mManualDragging) {\n                // We're not interested in programmatically generated changes to\n                // the progress bar's position.\n                return;\n            }\n\n            long duration = mFensterPlayer.getDuration();\n            long newposition = (duration * progress) / 1000L;\n            mFensterPlayer.seekTo((int) newposition);\n            if (mCurrentTime != null) {\n                mCurrentTime.setText(stringForTime((int) newposition));\n            }\n        }\n\n        public void onStopTrackingTouch(final SeekBar bar) {\n            mDragging = false;\n            setProgress();\n            updatePausePlay();\n            show(DEFAULT_TIMEOUT);\n\n            // Ensure that progress is properly updated in the future,\n            // the call to show() does not guarantee this because it is a\n            // no-op if we are already showing.\n            mHandler.sendEmptyMessage(SHOW_PROGRESS);\n        }\n    };\n    private ImageButton mPauseButton;\n    private ImageButton mNextButton;\n    private ImageButton mPrevButton;\n    private int lastPlayedSeconds = -1;\n\n    public MediaFensterPlayerController(final Context context) {\n        this(context, null);\n    }\n\n    public MediaFensterPlayerController(final Context context, final AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public MediaFensterPlayerController(final Context context, final AttributeSet attrs, final int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_media_controller, this);\n        initControllerView();\n    }\n\n    @Override\n    public void setMediaPlayer(final FensterPlayer fensterPlayer) {\n        mFensterPlayer = fensterPlayer;\n        updatePausePlay();\n    }\n\n    public void setVisibilityListener(final FensterPlayerControllerVisibilityListener visibilityListener) {\n        this.visibilityListener = visibilityListener;\n    }\n\n    private void initControllerView() {\n        bottomControlsArea = findViewById(R.id.media_controller_bottom_root);\n\n        gestureControllerView = (FensterGestureControllerView) findViewById(R.id.media_controller_gestures_area);\n        gestureControllerView.setFensterEventsListener(this);\n\n        mPauseButton = (ImageButton) findViewById(R.id.fen__media_controller_pause);\n        mPauseButton.requestFocus();\n        mPauseButton.setOnClickListener(mPauseListener);\n\n        mNextButton = (ImageButton) findViewById(R.id.fen__media_controller_next);\n        mPrevButton = (ImageButton) findViewById(R.id.fen__media_controller_previous);\n\n        mProgress = (SeekBar) findViewById(R.id.fen__media_controller_progress);\n        mProgress.setOnSeekBarChangeListener(mSeekListener);\n        mProgress.setMax(MAX_VIDEO_PROGRESS);\n\n        mVolume = (VolumeSeekBar) findViewById(R.id.fen__media_controller_volume);\n        mVolume.initialise(this);\n\n        mBrightness = (BrightnessSeekBar) findViewById(R.id.fen__media_controller_brightness);\n        mBrightness.initialise(this);\n\n        mEndTime = (TextView) findViewById(R.id.fen__media_controller_time);\n        mCurrentTime = (TextView) findViewById(R.id.fen__media_controller_time_current);\n        mFormatBuilder = new StringBuilder();\n        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());\n    }\n\n    /**\n     * Show the controller on screen. It will go away\n     * automatically after the default time of inactivity.\n     */\n    @Override\n    public void show() {\n        show(DEFAULT_TIMEOUT);\n    }\n\n    /**\n     * Show the controller on screen. It will go away\n     * automatically after 'timeout' milliseconds of inactivity.\n     *\n     * @param timeInMilliSeconds The timeout in milliseconds. Use 0 to show\n     *                           the controller until hide() is called.\n     */\n    @Override\n    public void show(final int timeInMilliSeconds) {\n        if (!mShowing) {\n            showBottomArea();\n            setProgress();\n            if (mPauseButton != null) {\n                mPauseButton.requestFocus();\n            }\n            mShowing = true;\n            setVisibility(View.VISIBLE);\n        }\n\n        updatePausePlay();\n\n        // cause the progress bar to be updated even if mShowing\n        // was already true.  This happens, for example, if we're\n        // paused with the progress bar showing the user hits play.\n        mHandler.sendEmptyMessage(SHOW_PROGRESS);\n\n        Message msg = mHandler.obtainMessage(FADE_OUT);\n        if (timeInMilliSeconds != 0) {\n            mHandler.removeMessages(FADE_OUT);\n            mHandler.sendMessageDelayed(msg, timeInMilliSeconds);\n        }\n\n        if (visibilityListener != null) {\n            visibilityListener.onControlsVisibilityChange(true);\n        }\n\n    }\n\n    private void showBottomArea() {\n        bottomControlsArea.setVisibility(View.VISIBLE);\n    }\n\n    public boolean isShowing() {\n        return mShowing;\n    }\n\n    public boolean isFirstTimeLoading() {\n        return mFirstTimeLoading;\n    }\n\n    /**\n     * Remove the controller from the screen.\n     */\n    @Override\n    public void hide() {\n        if (!mDragging) {\n            if (mShowing) {\n                try {\n                    mHandler.removeMessages(SHOW_PROGRESS);\n                    setVisibility(View.INVISIBLE);\n                } catch (final IllegalArgumentException ex) {\n                    Log.w(\"MediaController\", \"already removed\");\n                }\n                mShowing = false;\n            }\n            if (visibilityListener != null) {\n                visibilityListener.onControlsVisibilityChange(false);\n            }\n        }\n\n    }\n\n    private String stringForTime(final int timeMs) {\n        int totalSeconds = timeMs / 1000;\n\n        int seconds = totalSeconds % 60;\n        int minutes = (totalSeconds / 60) % 60;\n        int hours = totalSeconds / 3600;\n\n        mFormatBuilder.setLength(0);\n        if (hours > 0) {\n            return mFormatter.format(\"%d:%02d:%02d\", hours, minutes, seconds).toString();\n        } else {\n            return mFormatter.format(\"%02d:%02d\", minutes, seconds).toString();\n        }\n    }\n\n    private int setProgress() {\n        if (mFensterPlayer == null || mDragging) {\n            return 0;\n        }\n        int position = mFensterPlayer.getCurrentPosition();\n        int duration = mFensterPlayer.getDuration();\n        if (mProgress != null) {\n            if (duration > 0) {\n                // use long to avoid overflow\n                long pos = 1000L * position / duration;\n                mProgress.setProgress((int) pos);\n            }\n            int percent = mFensterPlayer.getBufferPercentage();\n            mProgress.setSecondaryProgress(percent * 10);\n        }\n\n        if (mEndTime != null) {\n            mEndTime.setText(stringForTime(duration));\n        }\n        if (mCurrentTime != null) {\n            mCurrentTime.setText(stringForTime(position));\n        }\n        final int playedSeconds = position / 1000;\n        if (lastPlayedSeconds != playedSeconds) {\n            lastPlayedSeconds = playedSeconds;\n        }\n        return position;\n    }\n\n    @Override\n    public boolean onTrackballEvent(final MotionEvent ev) {\n        show(DEFAULT_TIMEOUT);\n        return false;\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(final KeyEvent event) {\n        int keyCode = event.getKeyCode();\n        final boolean uniqueDown = event.getRepeatCount() == 0\n                && event.getAction() == KeyEvent.ACTION_DOWN;\n        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE\n                || keyCode == KeyEvent.KEYCODE_SPACE) {\n            if (uniqueDown) {\n                doPauseResume();\n                show(DEFAULT_TIMEOUT);\n                if (mPauseButton != null) {\n                    mPauseButton.requestFocus();\n                }\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n            if (uniqueDown && !mFensterPlayer.isPlaying()) {\n                mFensterPlayer.start();\n                updatePausePlay();\n                show(DEFAULT_TIMEOUT);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n            if (uniqueDown && mFensterPlayer.isPlaying()) {\n                mFensterPlayer.pause();\n                updatePausePlay();\n                show(DEFAULT_TIMEOUT);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN\n                || keyCode == KeyEvent.KEYCODE_VOLUME_UP\n                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE\n                || keyCode == KeyEvent.KEYCODE_CAMERA) {\n            // don't show the controls for volume adjustment\n            return super.dispatchKeyEvent(event);\n        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {\n            if (uniqueDown) {\n                hide();\n            }\n            return true;\n        }\n\n        show(DEFAULT_TIMEOUT);\n        return super.dispatchKeyEvent(event);\n    }\n\n    private void updatePausePlay() {\n        if (mPauseButton == null) {\n            return;\n        }\n\n        if (mFensterPlayer.isPlaying()) {\n            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);\n        } else {\n            mPauseButton.setImageResource(android.R.drawable.ic_media_play);\n        }\n    }\n\n    private void doPauseResume() {\n        if (mFensterPlayer.isPlaying()) {\n            mFensterPlayer.pause();\n        } else {\n            mFensterPlayer.start();\n        }\n        updatePausePlay();\n    }\n\n    @Override\n    public void setEnabled(final boolean enabled) {\n        if (mPauseButton != null) {\n            mPauseButton.setEnabled(enabled);\n        }\n        if (mNextButton != null) {\n            mNextButton.setEnabled(enabled);\n        }\n        if (mPrevButton != null) {\n            mPrevButton.setEnabled(enabled);\n        }\n        if (mProgress != null) {\n            mProgress.setEnabled(enabled);\n        }\n        if (mVolume != null) {\n            mVolume.setEnabled(enabled);\n        }\n        if (mBrightness != null) {\n            mBrightness.setEnabled(enabled);\n        }\n        super.setEnabled(enabled);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(MediaFensterPlayerController.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(MediaFensterPlayerController.class.getName());\n    }\n\n    @Override\n    public void onTap() {\n        Log.i(TAG, \"Single Tap Up\");\n    }\n\n    @Override\n    public void onHorizontalScroll(MotionEvent event, float delta) {\n        if (event.getPointerCount() == ONE_FINGER) {\n            updateVideoProgressBar(delta);\n        } else {\n            if (delta > 0) {\n                skipVideoForward();\n            } else {\n                skipVideoBackwards();\n            }\n        }\n    }\n\n    @Override\n    public void onVerticalScroll(MotionEvent event, float delta) {\n        if (event.getPointerCount() == ONE_FINGER) {\n            updateVolumeProgressBar(-delta);\n        } else {\n            updateBrightnessProgressBar(-delta);\n        }\n    }\n\n    @Override\n    public void onSwipeRight() {\n        skipVideoForward();\n    }\n\n    @Override\n    public void onSwipeLeft() {\n        skipVideoBackwards();\n    }\n\n    @Override\n    public void onSwipeBottom() {\n\n    }\n\n    @Override\n    public void onSwipeTop() {\n\n    }\n\n    @Override\n    public void onVolumeStartedDragging() {\n        mDragging = true;\n    }\n\n    @Override\n    public void onVolumeFinishedDragging() {\n        mDragging = false;\n    }\n\n    @Override\n    public void onBrigthnessStartedDragging() {\n        mDragging = true;\n    }\n\n    @Override\n    public void onBrightnessFinishedDragging() {\n        mDragging = false;\n    }\n\n    private void updateVolumeProgressBar(float delta) {\n        mVolume.manuallyUpdate(extractVerticalDeltaScale(delta, mVolume));\n    }\n\n    private void updateBrightnessProgressBar(float delta) {\n        mBrightness.manuallyUpdate((int) delta);\n    }\n\n    private void updateVideoProgressBar(float delta) {\n        mSeekListener.onProgressChanged(mProgress, extractHorizontalDeltaScale(delta, mProgress), true);\n    }\n\n    private void skipVideoForward() {\n        mSeekListener.onProgressChanged(mProgress, forwardSkippingUnit(), true);\n    }\n\n    private void skipVideoBackwards() {\n        mSeekListener.onProgressChanged(mProgress, backwardSkippingUnit(), true);\n    }\n\n    private int extractHorizontalDeltaScale(float deltaX, SeekBar seekbar) {\n        return extractDeltaScale(getWidth(), deltaX, seekbar);\n    }\n\n    private int extractVerticalDeltaScale(float deltaY, SeekBar seekbar) {\n        return extractDeltaScale(getHeight(), deltaY, seekbar);\n    }\n\n    private int extractDeltaScale(int availableSpace, float deltaX, SeekBar seekbar) {\n        int x = (int) deltaX;\n        float scale;\n        float progress = seekbar.getProgress();\n        final int max = seekbar.getMax();\n\n        if (x < 0) {\n            scale = (float) (x) / (float) (max - availableSpace);\n            progress = progress - (scale * progress);\n        } else {\n            scale = (float) (x) / (float) availableSpace;\n            progress += scale * max;\n        }\n\n        return (int) progress;\n    }\n\n    private int forwardSkippingUnit() {\n        return mProgress.getProgress() + SKIP_VIDEO_PROGRESS;\n    }\n\n    private int backwardSkippingUnit() {\n        return mProgress.getProgress() - SKIP_VIDEO_PROGRESS;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/SimpleMediaFensterPlayerController.java",
    "content": "package com.malmstein.fenster.controller;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.FrameLayout;\nimport android.widget.ImageButton;\nimport android.widget.ProgressBar;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.malmstein.fenster.R;\nimport com.malmstein.fenster.play.FensterPlayer;\nimport com.malmstein.fenster.play.FensterVideoStateListener;\nimport com.malmstein.fenster.view.FensterTouchRoot;\n\nimport java.util.Formatter;\nimport java.util.Locale;\n\n/**\n * Controller to manage syncing the ui models with the UI Controls and MediaPlayer.\n * <p/>\n * Note that the ui models have a narrow scope (i.e. chapter list, piece navigation),\n * their interaction is orchestrated by this controller.ø\n * <p/>\n * It's actually a view currently, as is the android MediaController.\n * (which is a bit odd and should be subject to change.)\n */\npublic final class SimpleMediaFensterPlayerController extends FrameLayout implements FensterPlayerController, FensterVideoStateListener, FensterTouchRoot.OnTouchReceiver {\n\n    public static final String TAG = \"PlayerController\";\n    public static final int DEFAULT_VIDEO_START = 0;\n    private static final int DEFAULT_TIMEOUT = 5000;\n\n    private static final int FADE_OUT = 1;\n    private static final int SHOW_PROGRESS = 2;\n\n    private FensterPlayerControllerVisibilityListener visibilityListener;\n    private FensterPlayer mFensterPlayer;\n    private boolean mShowing;\n    private boolean mDragging;\n\n    private boolean mLoading;\n    private boolean mFirstTimeLoading = true;\n\n    private StringBuilder mFormatBuilder;\n    private Formatter mFormatter;\n    private View bottomControlsRoot;\n    private View controlsRoot;\n    private ProgressBar mProgress;\n    private TextView mEndTime;\n    private TextView mCurrentTime;\n\n    private ImageButton mPauseButton;\n    private ImageButton mNextButton;\n    private ImageButton mPrevButton;\n    private ProgressBar loadingView;\n    private int lastPlayedSeconds = -1;\n\n    public SimpleMediaFensterPlayerController(final Context context) {\n        this(context, null);\n    }\n\n    public SimpleMediaFensterPlayerController(final Context context, final AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public SimpleMediaFensterPlayerController(final Context context, final AttributeSet attrs, final int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_simple_media_controller, this);\n        initControllerView();\n    }\n\n    @Override\n    public void setMediaPlayer(final FensterPlayer fensterPlayer) {\n        mFensterPlayer = fensterPlayer;\n        updatePausePlay();\n    }\n\n    @Override\n    public void setVisibilityListener(final FensterPlayerControllerVisibilityListener visibilityListener) {\n        this.visibilityListener = visibilityListener;\n    }\n\n    private void initControllerView() {\n        mPauseButton = (ImageButton) findViewById(R.id.fen__media_controller_pause);\n        mPauseButton.requestFocus();\n        mPauseButton.setOnClickListener(mPauseListener);\n\n        mNextButton = (ImageButton) findViewById(R.id.fen__media_controller_next);\n        mPrevButton = (ImageButton) findViewById(R.id.fen__media_controller_previous);\n\n        mProgress = (SeekBar) findViewById(R.id.fen__media_controller_progress);\n        SeekBar seeker = (SeekBar) mProgress;\n        seeker.setOnSeekBarChangeListener(mSeekListener);\n        mProgress.setMax(1000);\n\n        mEndTime = (TextView) findViewById(R.id.fen__media_controller_time);\n        mCurrentTime = (TextView) findViewById(R.id.fen__media_controller_time_current);\n        mFormatBuilder = new StringBuilder();\n        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());\n\n        FensterTouchRoot touchRoot = (FensterTouchRoot) findViewById(R.id.media_controller_touch_root);\n        touchRoot.setOnTouchReceiver(this);\n\n        bottomControlsRoot = findViewById(R.id.fen__media_controller_bottom_area);\n        bottomControlsRoot.setVisibility(View.INVISIBLE);\n\n        controlsRoot = findViewById(R.id.media_controller_controls_root);\n        controlsRoot.setVisibility(View.INVISIBLE);\n\n        loadingView = (ProgressBar) findViewById(R.id.fen__media_controller_loading_view);\n    }\n\n\n    /**\n     * Show the controller on screen. It will go away\n     * automatically after the default time of inactivity.\n     */\n    @Override\n    public void show() {\n        show(DEFAULT_TIMEOUT);\n    }\n\n    /**\n     * Show the controller on screen. It will go away\n     * automatically after 'timeout' milliseconds of inactivity.\n     *\n     * @param timeInMilliSeconds The timeout in milliseconds. Use 0 to show\n     *                           the controller until hide() is called.\n     */\n    @Override\n    public void show(final int timeInMilliSeconds) {\n        if (!mShowing) {\n            setProgress();\n            if (mPauseButton != null) {\n                mPauseButton.requestFocus();\n            }\n            mShowing = true;\n            setVisibility(View.VISIBLE);\n        }\n\n        updatePausePlay();\n\n        // cause the progress bar to be updated even if mShowing\n        // was already true.  This happens, for example, if we're\n        // paused with the progress bar showing the user hits play.\n        mHandler.sendEmptyMessage(SHOW_PROGRESS);\n\n        Message msg = mHandler.obtainMessage(FADE_OUT);\n        if (timeInMilliSeconds != 0) {\n            mHandler.removeMessages(FADE_OUT);\n            mHandler.sendMessageDelayed(msg, timeInMilliSeconds);\n        }\n\n        if (visibilityListener != null) {\n            visibilityListener.onControlsVisibilityChange(true);\n        }\n\n    }\n\n    public boolean isShowing() {\n        return mShowing;\n    }\n\n    public boolean isLoading() {\n        return mLoading;\n    }\n\n    public boolean isFirstTimeLoading() {\n        return mFirstTimeLoading;\n    }\n\n    /**\n     * Remove the controller from the screen.\n     */\n    @Override\n    public void hide() {\n\n        if (mShowing) {\n            try {\n                mHandler.removeMessages(SHOW_PROGRESS);\n                setVisibility(View.INVISIBLE);\n            } catch (final IllegalArgumentException ex) {\n                Log.w(\"MediaController\", \"already removed\");\n            }\n            mShowing = false;\n        }\n        if (visibilityListener != null) {\n            visibilityListener.onControlsVisibilityChange(false);\n        }\n    }\n\n    private String stringForTime(final int timeMs) {\n        int totalSeconds = timeMs / 1000;\n\n        int seconds = totalSeconds % 60;\n        int minutes = (totalSeconds / 60) % 60;\n        int hours = totalSeconds / 3600;\n\n        mFormatBuilder.setLength(0);\n        if (hours > 0) {\n            return mFormatter.format(\"%d:%02d:%02d\", hours, minutes, seconds).toString();\n        } else {\n            return mFormatter.format(\"%02d:%02d\", minutes, seconds).toString();\n        }\n    }\n\n    private int setProgress() {\n        if (mFensterPlayer == null || mDragging) {\n            return 0;\n        }\n        int position = mFensterPlayer.getCurrentPosition();\n        int duration = mFensterPlayer.getDuration();\n        if (mProgress != null) {\n            if (duration > 0) {\n                // use long to avoid overflow\n                long pos = 1000L * position / duration;\n                mProgress.setProgress((int) pos);\n            }\n            int percent = mFensterPlayer.getBufferPercentage();\n            mProgress.setSecondaryProgress(percent * 10);\n        }\n\n        if (mEndTime != null) {\n            mEndTime.setText(stringForTime(duration));\n        }\n        if (mCurrentTime != null) {\n            mCurrentTime.setText(stringForTime(position));\n        }\n        final int playedSeconds = position / 1000;\n        if (lastPlayedSeconds != playedSeconds) {\n            lastPlayedSeconds = playedSeconds;\n        }\n        return position;\n    }\n\n    @Override\n    public boolean onTrackballEvent(final MotionEvent ev) {\n        show(DEFAULT_TIMEOUT);\n        return false;\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(final KeyEvent event) {\n        int keyCode = event.getKeyCode();\n        final boolean uniqueDown = event.getRepeatCount() == 0\n                && event.getAction() == KeyEvent.ACTION_DOWN;\n        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE\n                || keyCode == KeyEvent.KEYCODE_SPACE) {\n            if (uniqueDown) {\n                doPauseResume();\n                show(DEFAULT_TIMEOUT);\n                if (mPauseButton != null) {\n                    mPauseButton.requestFocus();\n                }\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n            if (uniqueDown && !mFensterPlayer.isPlaying()) {\n                mFensterPlayer.start();\n                updatePausePlay();\n                show(DEFAULT_TIMEOUT);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP\n                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n            if (uniqueDown && mFensterPlayer.isPlaying()) {\n                mFensterPlayer.pause();\n                updatePausePlay();\n                show(DEFAULT_TIMEOUT);\n            }\n            return true;\n        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN\n                || keyCode == KeyEvent.KEYCODE_VOLUME_UP\n                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE\n                || keyCode == KeyEvent.KEYCODE_CAMERA) {\n            // don't show the controls for volume adjustment\n            return super.dispatchKeyEvent(event);\n        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {\n            if (uniqueDown) {\n                hide();\n            }\n            return true;\n        }\n\n        show(DEFAULT_TIMEOUT);\n        return super.dispatchKeyEvent(event);\n    }\n\n    private void updatePausePlay() {\n        if (mPauseButton == null) {\n            return;\n        }\n\n        if (mFensterPlayer.isPlaying()) {\n            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);\n        } else {\n            mPauseButton.setImageResource(android.R.drawable.ic_media_play);\n        }\n    }\n\n    private void doPauseResume() {\n        if (mFensterPlayer.isPlaying()) {\n            mFensterPlayer.pause();\n        } else {\n            mFensterPlayer.start();\n        }\n        updatePausePlay();\n    }\n\n    @Override\n    public void setEnabled(final boolean enabled) {\n\n        if (mPauseButton != null) {\n            mPauseButton.setEnabled(enabled);\n        }\n\n        if (mNextButton != null) {\n            mNextButton.setEnabled(enabled);\n        }\n        if (mPrevButton != null) {\n            mPrevButton.setEnabled(enabled);\n        }\n        if (mProgress != null) {\n            mProgress.setEnabled(enabled);\n        }\n        super.setEnabled(enabled);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(SimpleMediaFensterPlayerController.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(SimpleMediaFensterPlayerController.class.getName());\n    }\n\n    @Override\n    public void onFirstVideoFrameRendered() {\n        controlsRoot.setVisibility(View.VISIBLE);\n        bottomControlsRoot.setVisibility(View.VISIBLE);\n        mFirstTimeLoading = false;\n    }\n\n    @Override\n    public void onPlay() {\n        hideLoadingView();\n    }\n\n    @Override\n    public void onBuffer() {\n        showLoadingView();\n    }\n\n    @Override\n    public boolean onStopWithExternalError(int position) {\n        return false;\n    }\n\n    private void hideLoadingView() {\n        hide();\n        loadingView.setVisibility(View.GONE);\n\n        mLoading = false;\n    }\n\n    private void showLoadingView() {\n        mLoading = true;\n        loadingView.setVisibility(View.VISIBLE);\n    }\n\n    // There are two scenarios that can trigger the seekbar listener to trigger:\n    //\n    // The first is the user using the touchpad to adjust the posititon of the\n    // seekbar's thumb. In this case onStartTrackingTouch is called followed by\n    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.\n    // We're setting the field \"mDragging\" to true for the duration of the dragging\n    // session to avoid jumps in the position in case of ongoing playback.\n    //\n    // The second scenario involves the user operating the scroll ball, in this\n    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,\n    // we will simply apply the updated position without suspending regular updates.\n    private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {\n        public void onStartTrackingTouch(final SeekBar bar) {\n            show(3600000);\n\n            mDragging = true;\n\n            // By removing these pending progress messages we make sure\n            // that a) we won't update the progress while the user adjusts\n            // the seekbar and b) once the user is done dragging the thumb\n            // we will post one of these messages to the queue again and\n            // this ensures that there will be exactly one message queued up.\n            mHandler.removeMessages(SHOW_PROGRESS);\n        }\n\n        public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {\n            if (!fromuser) {\n                // We're not interested in programmatically generated changes to\n                // the progress bar's position.\n                return;\n            }\n\n            long duration = mFensterPlayer.getDuration();\n            long newposition = (duration * progress) / 1000L;\n            mFensterPlayer.seekTo((int) newposition);\n            if (mCurrentTime != null) {\n                mCurrentTime.setText(stringForTime((int) newposition));\n            }\n        }\n\n        public void onStopTrackingTouch(final SeekBar bar) {\n            mDragging = false;\n            setProgress();\n            updatePausePlay();\n            show(DEFAULT_TIMEOUT);\n\n            // Ensure that progress is properly updated in the future,\n            // the call to show() does not guarantee this because it is a\n            // no-op if we are already showing.\n            mHandler.sendEmptyMessage(SHOW_PROGRESS);\n        }\n    };\n\n    private final Handler mHandler = new Handler() {\n        @Override\n        public void handleMessage(final Message msg) {\n            int pos;\n            switch (msg.what) {\n                case FADE_OUT:\n                    if (mFensterPlayer.isPlaying()) {\n                        hide();\n                    } else {\n                        // re-schedule to check again\n                        Message fadeMessage = obtainMessage(FADE_OUT);\n                        removeMessages(FADE_OUT);\n                        sendMessageDelayed(fadeMessage, DEFAULT_TIMEOUT);\n                    }\n                    break;\n                case SHOW_PROGRESS:\n                    pos = setProgress();\n                    if (!mDragging && mShowing && mFensterPlayer.isPlaying()) {\n                        final Message message = obtainMessage(SHOW_PROGRESS);\n                        sendMessageDelayed(message, 1000 - (pos % 1000));\n                    }\n                    break;\n            }\n        }\n    };\n\n    private final OnClickListener mPauseListener = new OnClickListener() {\n        public void onClick(final View v) {\n            doPauseResume();\n            show(DEFAULT_TIMEOUT);\n        }\n    };\n\n    /**\n     * Called by ViewTouchRoot on user touches,\n     * so we can avoid hiding the ui while the user is interacting.\n     */\n    @Override\n    public void onControllerUiTouched() {\n        if (mShowing) {\n            Log.d(TAG, \"controller ui touch received!\");\n            show();\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterEventsListener.java",
    "content": "package com.malmstein.fenster.gestures;\n\nimport android.view.MotionEvent;\n\npublic interface FensterEventsListener {\n\n    void onTap();\n\n    void onHorizontalScroll(MotionEvent event, float delta);\n\n    void onVerticalScroll(MotionEvent event, float delta);\n\n    void onSwipeRight();\n\n    void onSwipeLeft();\n\n    void onSwipeBottom();\n\n    void onSwipeTop();\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterGestureControllerView.java",
    "content": "package com.malmstein.fenster.gestures;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewConfiguration;\n\npublic class FensterGestureControllerView extends View{\n\n    private GestureDetector gestureDetector;\n    private FensterEventsListener listener;\n\n    public FensterGestureControllerView(Context context) {\n        super(context);\n    }\n\n    public FensterGestureControllerView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public FensterGestureControllerView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        setClickable(true);\n        setFocusable(true);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        mayNotifyGestureDetector(event);\n        return true;\n    }\n\n    private void mayNotifyGestureDetector(MotionEvent event){\n        gestureDetector.onTouchEvent(event);\n    }\n\n    public void setFensterEventsListener(FensterEventsListener listener){\n        gestureDetector = new GestureDetector(getContext(), new FensterGestureListener(listener, ViewConfiguration.get(getContext())));\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterGestureListener.java",
    "content": "package com.malmstein.fenster.gestures;\n\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.ViewConfiguration;\n\npublic class FensterGestureListener implements GestureDetector.OnGestureListener {\n\n    private static final int SWIPE_THRESHOLD = 100;\n    private final int minFlingVelocity;\n\n    public static final String TAG = \"FensterGestureListener\";\n    private final FensterEventsListener listener;\n\n    public FensterGestureListener(FensterEventsListener listener, ViewConfiguration viewConfiguration) {\n        this.listener = listener;\n        minFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();\n    }\n\n    @Override\n    public boolean onSingleTapUp(MotionEvent e) {\n        listener.onTap();\n        return false;\n    }\n\n    @Override\n    public void onLongPress(MotionEvent e) {\n        // Touch has been long enough to indicate a long press.\n        // Does not indicate motion is complete yet (no up event necessarily)\n        Log.i(TAG, \"Long Press\");\n    }\n\n    @Override\n    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {\n        Log.i(TAG, \"Scroll\");\n\n        float deltaY = e2.getY() - e1.getY();\n        float deltaX = e2.getX() - e1.getX();\n\n        if (Math.abs(deltaX) > Math.abs(deltaY)) {\n            if (Math.abs(deltaX) > SWIPE_THRESHOLD) {\n                listener.onHorizontalScroll(e2, deltaX);\n                if (deltaX > 0) {\n                    Log.i(TAG, \"Slide right\");\n                } else {\n                    Log.i(TAG, \"Slide left\");\n                }\n            }\n        } else {\n            if (Math.abs(deltaY) > SWIPE_THRESHOLD) {\n                listener.onVerticalScroll(e2, deltaY);\n                if (deltaY > 0) {\n                    Log.i(TAG, \"Slide down\");\n                } else {\n                    Log.i(TAG, \"Slide up\");\n                }\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n        // Fling event occurred.  Notification of this one happens after an \"up\" event.\n        Log.i(TAG, \"Fling\");\n        boolean result = false;\n        try {\n            float diffY = e2.getY() - e1.getY();\n            float diffX = e2.getX() - e1.getX();\n            if (Math.abs(diffX) > Math.abs(diffY)) {\n                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > minFlingVelocity) {\n                    if (diffX > 0) {\n                        listener.onSwipeRight();\n                    } else {\n                        listener.onSwipeLeft();\n                    }\n                }\n                result = true;\n            } else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > minFlingVelocity) {\n                if (diffY > 0) {\n                    listener.onSwipeBottom();\n                } else {\n                    listener.onSwipeTop();\n                }\n            }\n            result = true;\n\n        } catch (Exception exception) {\n            exception.printStackTrace();\n        }\n        return result;\n    }\n\n    @Override\n    public void onShowPress(MotionEvent e) {\n        Log.i(TAG, \"Show Press\");\n    }\n\n    @Override\n    public boolean onDown(MotionEvent e) {\n        Log.i(TAG, \"Down\");\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/helper/BrightnessHelper.java",
    "content": "package com.malmstein.fenster.helper;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.provider.Settings;\n\npublic class BrightnessHelper {\n\n    public static void setBrightness(Context context, int brightness){\n        ContentResolver cResolver = context.getContentResolver();\n        Settings.System.putInt(cResolver, Settings.System.SCREEN_BRIGHTNESS, brightness);\n    }\n\n    public static int getBrightness(Context context) {\n        ContentResolver cResolver = context.getContentResolver();\n        try {\n            return Settings.System.getInt(cResolver, Settings.System.SCREEN_BRIGHTNESS);\n        } catch (Settings.SettingNotFoundException e) {\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterPlayer.java",
    "content": "package com.malmstein.fenster.play;\n\npublic interface FensterPlayer {\n    void start();\n\n    void pause();\n\n    int getDuration();\n\n    /**\n     * @return current playback position in milliseconds.\n     */\n    int getCurrentPosition();\n\n    void seekTo(int pos);\n\n    boolean isPlaying();\n\n    int getBufferPercentage();\n\n    boolean canPause();\n\n    boolean canSeekBackward();\n\n    boolean canSeekForward();\n\n    /**\n     * Get the audio session id for the player used by this VideoView. This can be used to\n     * apply audio effects to the audio track of a video.\n     *\n     * @return The audio session, or 0 if there was an error.\n     */\n    int getAudioSessionId();\n\n}"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterVideoFragment.java",
    "content": "package com.malmstein.fenster.play;\n\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.malmstein.fenster.R;\nimport com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;\nimport com.malmstein.fenster.controller.FensterPlayerController;\nimport com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;\nimport com.malmstein.fenster.view.FensterLoadingView;\nimport com.malmstein.fenster.view.FensterVideoView;\n\npublic class FensterVideoFragment extends Fragment implements FensterVideoStateListener {\n\n    private View contentView;\n    private FensterVideoView textureView;\n    private FensterPlayerController fensterPlayerController;\n    private FensterLoadingView fensterLoadingView;\n\n    public FensterVideoFragment() {\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        contentView = inflater.inflate(R.layout.fen__fragment_fenster_video, container);\n        textureView = (FensterVideoView) contentView.findViewById(R.id.fen__play_video_texture);\n        fensterPlayerController = (FensterPlayerController) contentView.findViewById(R.id.fen__play_video_controller);\n        fensterLoadingView = (FensterLoadingView) contentView.findViewById(R.id.fen__play_video_loading);\n        return contentView;\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        initVideo();\n    }\n\n    private void initVideo() {\n        textureView.setMediaController(fensterPlayerController);\n        textureView.setOnPlayStateListener(this);\n    }\n\n    public void playExampleVideo() {\n        textureView.setVideo(\"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\",\n                SimpleMediaFensterPlayerController.DEFAULT_VIDEO_START);\n        textureView.start();\n    }\n\n    public void setVisibilityListener(FensterPlayerControllerVisibilityListener visibilityListener) {\n        fensterPlayerController.setVisibilityListener(visibilityListener);\n    }\n\n    public void showFensterController() {\n        fensterLoadingView.hide();\n        fensterPlayerController.show();\n    }\n\n    private void showLoadingView(){\n        fensterLoadingView.show();\n        fensterPlayerController.hide();\n    }\n\n    @Override\n    public void onFirstVideoFrameRendered() {\n        fensterPlayerController.show();\n    }\n\n    @Override\n    public void onPlay() {\n        showFensterController();\n    }\n\n    @Override\n    public void onBuffer() {\n        showLoadingView();\n    }\n\n    @Override\n    public boolean onStopWithExternalError(int position) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterVideoStateListener.java",
    "content": "package com.malmstein.fenster.play;\n\npublic interface FensterVideoStateListener {\n\n    void onFirstVideoFrameRendered();\n\n    void onPlay();\n\n    void onBuffer();\n\n    boolean onStopWithExternalError(int position);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/seekbar/BrightnessSeekBar.java",
    "content": "package com.malmstein.fenster.seekbar;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.SeekBar;\n\nimport com.malmstein.fenster.helper.BrightnessHelper;\n\npublic class BrightnessSeekBar extends SeekBar {\n\n    public static final int MAX_BRIGHTNESS = 255;\n    public static final int MIN_BRIGHTNESS = 0;\n    public final OnSeekBarChangeListener brightnessSeekListener = new OnSeekBarChangeListener() {\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int brightness, boolean fromUser) {\n            setBrightness(brightness);\n            setProgress(brightness);\n        }\n\n        @Override\n        public void onStartTrackingTouch(SeekBar seekBar) {\n            brightnessListener.onBrigthnessStartedDragging();\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n            brightnessListener.onBrightnessFinishedDragging();\n        }\n    };\n    private Listener brightnessListener;\n\n    public BrightnessSeekBar(Context context) {\n        super(context);\n    }\n\n    public BrightnessSeekBar(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public BrightnessSeekBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(BrightnessSeekBar.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(BrightnessSeekBar.class.getName());\n    }\n\n    public void initialise(Listener brightnessListener) {\n        this.setMax(MAX_BRIGHTNESS);\n        this.setOnSeekBarChangeListener(brightnessSeekListener);\n        this.brightnessListener = brightnessListener;\n        manuallyUpdate(BrightnessHelper.getBrightness(getContext()));\n    }\n\n    public void setBrightness(int brightness) {\n        if (brightness < MIN_BRIGHTNESS) {\n            brightness = MIN_BRIGHTNESS;\n        } else if (brightness > MAX_BRIGHTNESS) {\n            brightness = MAX_BRIGHTNESS;\n        }\n\n        BrightnessHelper.setBrightness(getContext(), brightness);\n    }\n\n    public void manuallyUpdate(int update) {\n        brightnessSeekListener.onProgressChanged(this, update, true);\n    }\n\n    public interface Listener {\n        void onBrigthnessStartedDragging();\n\n        void onBrightnessFinishedDragging();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/seekbar/VolumeSeekBar.java",
    "content": "package com.malmstein.fenster.seekbar;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.media.AudioManager;\nimport android.util.AttributeSet;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.SeekBar;\n\npublic class VolumeSeekBar extends SeekBar {\n\n    public final OnSeekBarChangeListener volumeSeekListener = new OnSeekBarChangeListener() {\n        @Override\n        public void onProgressChanged(SeekBar seekBar, int vol, boolean fromUser) {\n            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0);\n        }\n\n        @Override\n        public void onStartTrackingTouch(SeekBar seekBar) {\n            volumeListener.onVolumeStartedDragging();\n        }\n\n        @Override\n        public void onStopTrackingTouch(SeekBar seekBar) {\n            volumeListener.onVolumeFinishedDragging();\n        }\n    };\n\n    private AudioManager audioManager;\n    private Listener volumeListener;\n    private BroadcastReceiver volumeReceiver = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            updateVolumeProgress();\n        }\n    };\n\n    public VolumeSeekBar(Context context) {\n        super(context);\n    }\n\n    public VolumeSeekBar(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public VolumeSeekBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(VolumeSeekBar.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(VolumeSeekBar.class.getName());\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        registerVolumeReceiver();\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        unregisterVolumeReceiver();\n        super.onDetachedFromWindow();\n    }\n\n    public void initialise(final Listener volumeListener) {\n        this.audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);\n        this.volumeListener = volumeListener;\n\n        this.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));\n        this.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));\n        this.setOnSeekBarChangeListener(volumeSeekListener);\n    }\n\n    private void updateVolumeProgress() {\n        this.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));\n    }\n\n    private void registerVolumeReceiver() {\n        getContext().registerReceiver(volumeReceiver, new IntentFilter(\"android.media.VOLUME_CHANGED_ACTION\"));\n    }\n\n    private void unregisterVolumeReceiver() {\n        getContext().unregisterReceiver(volumeReceiver);\n    }\n\n    public void manuallyUpdate(int update) {\n        volumeSeekListener.onProgressChanged(this, update, true);\n    }\n\n    public interface Listener {\n        void onVolumeStartedDragging();\n\n        void onVolumeFinishedDragging();\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterLoadingView.java",
    "content": "package com.malmstein.fenster.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.widget.FrameLayout;\n\nimport com.malmstein.fenster.R;\n\npublic class FensterLoadingView extends FrameLayout {\n\n    public FensterLoadingView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public FensterLoadingView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_loading, this);\n    }\n\n    public void show() {\n        setVisibility(VISIBLE);\n    }\n\n    public void hide() {\n        setVisibility(GONE);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterTouchRoot.java",
    "content": "package com.malmstein.fenster.view;\n\nimport android.content.Context;\nimport android.os.SystemClock;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.widget.FrameLayout;\n\n/**\n * A custom layout we put as a layout root to get notified about any screen touches.\n */\npublic final class FensterTouchRoot extends FrameLayout {\n\n    public static final int MIN_INTERCEPTION_TIME = 1000;\n    private long lastInterception;\n\n    private OnTouchReceiver touchReceiver;\n\n    public FensterTouchRoot(final Context context) {\n        super(context);\n    }\n\n    public FensterTouchRoot(final Context context, final AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public FensterTouchRoot(final Context context, final AttributeSet attrs, final int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    public boolean dispatchTouchEvent(final MotionEvent ev) {\n        if (touchReceiver != null) {\n            final long timeStamp = SystemClock.elapsedRealtime();\n            // we throttle the touch event dispatch to avoid event spam\n            if (timeStamp - lastInterception > MIN_INTERCEPTION_TIME) {\n                lastInterception = timeStamp;\n                touchReceiver.onControllerUiTouched();\n            }\n        }\n        return super.dispatchTouchEvent(ev);\n    }\n\n    public void setOnTouchReceiver(final OnTouchReceiver receiver) {\n        this.touchReceiver = receiver;\n    }\n\n    public interface OnTouchReceiver {\n        void onControllerUiTouched();\n    }\n}"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterVideoView.java",
    "content": "package com.malmstein.fenster.view;\n\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.TypedArray;\nimport android.graphics.SurfaceTexture;\nimport android.media.AudioManager;\nimport android.media.MediaPlayer;\nimport android.media.MediaPlayer.OnCompletionListener;\nimport android.media.MediaPlayer.OnErrorListener;\nimport android.media.MediaPlayer.OnInfoListener;\nimport android.net.Uri;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.Surface;\nimport android.view.TextureView;\nimport android.view.accessibility.AccessibilityEvent;\nimport android.view.accessibility.AccessibilityNodeInfo;\nimport android.widget.MediaController;\n\nimport com.malmstein.fenster.R;\nimport com.malmstein.fenster.controller.FensterPlayerController;\nimport com.malmstein.fenster.play.FensterPlayer;\nimport com.malmstein.fenster.play.FensterVideoStateListener;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * Displays a video file.  The VideoView class\n * can load images from various sources (such as resources or content\n * providers), takes care of computing its measurement from the video so that\n * it can be used in any layout manager, and provides various display options\n * such as scaling and tinting.<p>\n * <p/>\n * <em>Note: VideoView does not retain its full state when going into the\n * background.</em>  In particular, it does not restore the current play state,\n * play position, selected tracks added via\n * {@link android.app.Activity#onSaveInstanceState} and\n * {@link android.app.Activity#onRestoreInstanceState}.<p>\n * Also note that the audio session id (from {@link #getAudioSessionId}) may\n * change from its previously returned value when the VideoView is restored.\n */\n\npublic class FensterVideoView extends TextureView implements MediaController.MediaPlayerControl, FensterPlayer {\n\n    public static final String TAG = \"TextureVideoView\";\n    public static final int VIDEO_BEGINNING = 0;\n\n    public enum ScaleType {\n        SCALE_TO_FIT, CROP\n    }\n\n    // all possible internal states\n    private static final int STATE_ERROR = -1;\n    private static final int STATE_IDLE = 0;\n    private static final int STATE_PREPARING = 1;\n    private static final int STATE_PREPARED = 2;\n    private static final int STATE_PLAYING = 3;\n    private static final int STATE_PAUSED = 4;\n    private static final int STATE_PLAYBACK_COMPLETED = 5;\n    private static final int MILLIS_IN_SEC = 1000;\n\n    // collaborators / delegates / composites .. discuss\n    private final VideoSizeCalculator videoSizeCalculator;\n    // mCurrentState is a VideoView object's current state.\n    // mTargetState is the state that a method caller intends to reach.\n    // For instance, regardless the VideoView object's current state,\n    // calling pause() intends to bring the object to a target state\n    // of STATE_PAUSED.\n    private int mCurrentState = STATE_IDLE;\n    private int mTargetState = STATE_IDLE;\n\n    private ScaleType mScaleType;\n\n    private Uri mUri;\n\n    private AssetFileDescriptor mAssetFileDescriptor;\n    private Map<String, String> mHeaders;\n    private SurfaceTexture mSurfaceTexture;\n    private MediaPlayer mMediaPlayer = null;\n    private FensterPlayerController fensterPlayerController;\n    private OnCompletionListener mOnCompletionListener;\n    private MediaPlayer.OnPreparedListener mOnPreparedListener;\n    private OnErrorListener mOnErrorListener;\n    private OnInfoListener mOnInfoListener;\n    private int mAudioSession;\n    private int mSeekWhenPrepared;  // recording the seek position while preparing\n    private int mCurrentBufferPercentage;\n    private boolean mCanPause;\n    private boolean mCanSeekBack;\n    private boolean mCanSeekForward;\n    private FensterVideoStateListener onPlayStateListener;\n\n    private AlertDialog errorDialog;\n\n    public FensterVideoView(final Context context, final AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public FensterVideoView(final Context context, final AttributeSet attrs, final int defStyle) {\n        super(context, attrs, defStyle);\n        applyCustomAttributes(context, attrs);\n        videoSizeCalculator = new VideoSizeCalculator();\n        initVideoView();\n    }\n\n    private void applyCustomAttributes(Context context, AttributeSet attrs) {\n        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FensterVideoView);\n        if (typedArray == null) {\n            return;\n        }\n        int[] attrsValues = {R.attr.scaleType};\n        TypedArray scaleTypedArray = context.obtainStyledAttributes(attrs, attrsValues);\n        if (scaleTypedArray != null) {\n            try {\n                int scaleTypeId = typedArray.getInt(0, 0);\n                mScaleType = ScaleType.values()[scaleTypeId];\n            } finally {\n                typedArray.recycle();\n            }\n        } else {\n            mScaleType = ScaleType.SCALE_TO_FIT;\n        }\n    }\n\n    @Override\n    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {\n        VideoSizeCalculator.Dimens dimens = videoSizeCalculator.measure(widthMeasureSpec, heightMeasureSpec);\n        setMeasuredDimension(dimens.getWidth(), dimens.getHeight());\n    }\n\n    @Override\n    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {\n        super.onInitializeAccessibilityEvent(event);\n        event.setClassName(FensterVideoView.class.getName());\n    }\n\n    @Override\n    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {\n        super.onInitializeAccessibilityNodeInfo(info);\n        info.setClassName(FensterVideoView.class.getName());\n    }\n\n    public int resolveAdjustedSize(final int desiredSize, final int measureSpec) {\n        return getDefaultSize(desiredSize, measureSpec);\n    }\n\n    private void initVideoView() {\n        videoSizeCalculator.setVideoSize(0, 0);\n\n        setSurfaceTextureListener(mSTListener);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        mCurrentState = STATE_IDLE;\n        mTargetState = STATE_IDLE;\n        setOnInfoListener(onInfoToPlayStateListener);\n    }\n\n    private void disableFileDescriptor() {\n        mAssetFileDescriptor = null;\n    }\n\n    public void setVideo(final String path) {\n        disableFileDescriptor();\n        setVideo(Uri.parse(path), VIDEO_BEGINNING);\n    }\n\n    public void setVideo(final String url, final int seekInSeconds) {\n        disableFileDescriptor();\n        setVideo(Uri.parse(url), seekInSeconds);\n    }\n\n    public void setVideo(final Uri uri, final int seekInSeconds) {\n        disableFileDescriptor();\n        setVideoURI(uri, null, seekInSeconds);\n    }\n\n    public void setVideo(final AssetFileDescriptor assetFileDescriptor) {\n        mAssetFileDescriptor = assetFileDescriptor;\n        setVideoURI(null, null, VIDEO_BEGINNING);\n    }\n\n    public void setVideo(final AssetFileDescriptor assetFileDescriptor, final int seekInSeconds) {\n        mAssetFileDescriptor = assetFileDescriptor;\n        setVideoURI(null, null, seekInSeconds);\n    }\n\n    /**\n     * Set the scale type of the video, needs to be set after setVideo() has been called\n     *\n     * @param scaleType\n     */\n    private void setScaleType(ScaleType scaleType) {\n        switch (scaleType) {\n            case SCALE_TO_FIT:\n                mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);\n                break;\n            case CROP:\n                mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);\n                break;\n        }\n    }\n\n    private void setVideoURI(final Uri uri, final Map<String, String> headers, final int seekInSeconds) {\n        Log.d(TAG, \"start playing: \" + uri);\n        mUri = uri;\n        mHeaders = headers;\n        mSeekWhenPrepared = seekInSeconds * 1000;\n        openVideo();\n        requestLayout();\n        invalidate();\n    }\n\n    public void stopPlayback() {\n        if (mMediaPlayer != null) {\n            mMediaPlayer.stop();\n            mMediaPlayer.release();\n            mMediaPlayer = null;\n            setKeepScreenOn(false);\n            mCurrentState = STATE_IDLE;\n            mTargetState = STATE_IDLE;\n        }\n    }\n\n    private void openVideo() {\n        if (notReadyForPlaybackJustYetWillTryAgainLater()) {\n            return;\n        }\n        tellTheMusicPlaybackServiceToPause();\n\n        // we shouldn't clear the target state, because somebody might have called start() previously\n        release(false);\n        try {\n            mMediaPlayer = new MediaPlayer();\n\n            if (mAudioSession != 0) {\n                mMediaPlayer.setAudioSessionId(mAudioSession);\n            } else {\n                mAudioSession = mMediaPlayer.getAudioSessionId();\n            }\n            mMediaPlayer.setOnPreparedListener(mPreparedListener);\n            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);\n            mMediaPlayer.setOnCompletionListener(mCompletionListener);\n            mMediaPlayer.setOnErrorListener(mErrorListener);\n            mMediaPlayer.setOnInfoListener(mInfoListener);\n            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);\n            mCurrentBufferPercentage = 0;\n\n            setDataSource();\n            setScaleType(mScaleType);\n\n            mMediaPlayer.setSurface(new Surface(mSurfaceTexture));\n            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);\n            mMediaPlayer.setScreenOnWhilePlaying(true);\n            mMediaPlayer.prepareAsync();\n\n            // we don't set the target state here either, but preserve the target state that was there before.\n            mCurrentState = STATE_PREPARING;\n            attachMediaController();\n        } catch (final IOException | IllegalArgumentException ex) {\n            notifyUnableToOpenContent(ex);\n        }\n    }\n\n    private void setDataSource() throws IOException {\n        if (mAssetFileDescriptor != null) {\n            mMediaPlayer.setDataSource(\n                    mAssetFileDescriptor.getFileDescriptor(),\n                    mAssetFileDescriptor.getStartOffset(),\n                    mAssetFileDescriptor.getLength()\n            );\n        } else {\n            mMediaPlayer.setDataSource(getContext(), mUri, mHeaders);\n        }\n    }\n\n    private boolean notReadyForPlaybackJustYetWillTryAgainLater() {\n        return mSurfaceTexture == null;\n    }\n\n    private void tellTheMusicPlaybackServiceToPause() {\n        // these constants need to be published somewhere in the framework.\n        Intent i = new Intent(\"com.android.music.musicservicecommand\");\n        i.putExtra(\"command\", \"pause\");\n        getContext().sendBroadcast(i);\n    }\n\n    private void notifyUnableToOpenContent(final Exception ex) {\n        Log.w(\"Unable to open content:\" + mUri, ex);\n        mCurrentState = STATE_ERROR;\n        mTargetState = STATE_ERROR;\n        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);\n    }\n\n    public void setMediaController(final FensterPlayerController controller) {\n        hideMediaController();\n        fensterPlayerController = controller;\n        attachMediaController();\n    }\n\n    private void attachMediaController() {\n        if (mMediaPlayer != null && fensterPlayerController != null) {\n            fensterPlayerController.setMediaPlayer(this);\n//            View anchorView = this.getParent() instanceof View ? (View) this.getParent() : this;\n//            fensterPlayerController.setAnchorView(anchorView);\n            fensterPlayerController.setEnabled(isInPlaybackState());\n        }\n    }\n\n    private MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {\n        @Override\n        public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) {\n            videoSizeCalculator.setVideoSize(mp.getVideoWidth(), mp.getVideoHeight());\n            if (videoSizeCalculator.hasASizeYet()) {\n                requestLayout();\n            }\n        }\n    };\n\n    private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {\n        @Override\n        public void onPrepared(final MediaPlayer mp) {\n            mCurrentState = STATE_PREPARED;\n\n            mCanPause = true;\n            mCanSeekBack = true;\n            mCanSeekForward = true;\n\n            if (mOnPreparedListener != null) {\n                mOnPreparedListener.onPrepared(mMediaPlayer);\n            }\n            if (fensterPlayerController != null) {\n                fensterPlayerController.setEnabled(true);\n            }\n            videoSizeCalculator.setVideoSize(mp.getVideoWidth(), mp.getVideoHeight());\n\n            int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call\n            if (seekToPosition != 0) {\n                seekTo(seekToPosition);\n            }\n\n            if (mTargetState == STATE_PLAYING) {\n                start();\n                showMediaController();\n            } else if (pausedAt(seekToPosition)) {\n                showStickyMediaController();\n            }\n        }\n    };\n\n    private boolean pausedAt(final int seekToPosition) {\n        return !isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0);\n    }\n\n    private void showStickyMediaController() {\n        if (fensterPlayerController != null) {\n            fensterPlayerController.show(0);\n        }\n    }\n\n    private OnCompletionListener mCompletionListener = new OnCompletionListener() {\n\n        @Override\n        public void onCompletion(final MediaPlayer mp) {\n            setKeepScreenOn(false);\n            mCurrentState = STATE_PLAYBACK_COMPLETED;\n            mTargetState = STATE_PLAYBACK_COMPLETED;\n            hideMediaController();\n            if (mOnCompletionListener != null) {\n                mOnCompletionListener.onCompletion(mMediaPlayer);\n            }\n        }\n    };\n\n    private OnInfoListener mInfoListener = new OnInfoListener() {\n        @Override\n        public boolean onInfo(final MediaPlayer mp, final int arg1, final int arg2) {\n            if (mOnInfoListener != null) {\n                mOnInfoListener.onInfo(mp, arg1, arg2);\n            }\n            return true;\n        }\n    };\n\n    private OnErrorListener mErrorListener = new OnErrorListener() {\n        @Override\n        public boolean onError(final MediaPlayer mp, final int frameworkError, final int implError) {\n            Log.d(TAG, \"Error: \" + frameworkError + \",\" + implError);\n            if (mCurrentState == STATE_ERROR) {\n                return true;\n            }\n            mCurrentState = STATE_ERROR;\n            mTargetState = STATE_ERROR;\n            hideMediaController();\n\n            if (allowPlayStateToHandle(frameworkError)) {\n                return true;\n            }\n\n            if (allowErrorListenerToHandle(frameworkError, implError)) {\n                return true;\n            }\n\n            handleError(frameworkError);\n            return true;\n        }\n    };\n\n    private void hideMediaController() {\n        if (fensterPlayerController != null) {\n            fensterPlayerController.hide();\n        }\n    }\n\n    private void showMediaController() {\n        if (fensterPlayerController != null) {\n            fensterPlayerController.show();\n        }\n    }\n\n    private boolean allowPlayStateToHandle(final int frameworkError) {\n        if (frameworkError == MediaPlayer.MEDIA_ERROR_UNKNOWN || frameworkError == MediaPlayer.MEDIA_ERROR_IO) {\n            Log.e(TAG, \"TextureVideoView error. File or network related operation errors.\");\n            if (hasPlayStateListener()) {\n                return onPlayStateListener.onStopWithExternalError(mMediaPlayer.getCurrentPosition() / MILLIS_IN_SEC);\n            }\n        }\n        return false;\n    }\n\n    private boolean allowErrorListenerToHandle(final int frameworkError, final int implError) {\n        if (mOnErrorListener != null) {\n            return mOnErrorListener.onError(mMediaPlayer, frameworkError, implError);\n        }\n\n        return false;\n    }\n\n    private void handleError(final int frameworkError) {\n        if (getWindowToken() != null) {\n            if (errorDialog != null && errorDialog.isShowing()) {\n                Log.d(TAG, \"Dismissing last error dialog for a new one\");\n                errorDialog.dismiss();\n            }\n            errorDialog = createErrorDialog(this.getContext(), mOnCompletionListener, mMediaPlayer, getErrorMessage(frameworkError));\n            errorDialog.show();\n        }\n    }\n\n    private static AlertDialog createErrorDialog(final Context context, final OnCompletionListener completionListener, final MediaPlayer mediaPlayer, final int errorMessage) {\n        return new AlertDialog.Builder(context)\n                .setMessage(errorMessage)\n                .setPositiveButton(\n                        android.R.string.ok,\n                        new DialogInterface.OnClickListener() {\n                            public void onClick(final DialogInterface dialog, final int whichButton) {\n                                    /* If we get here, there is no onError listener, so\n                                     * at least inform them that the video is over.\n                                     */\n                                if (completionListener != null) {\n                                    completionListener.onCompletion(mediaPlayer);\n                                }\n                            }\n                        }\n                )\n                .setCancelable(false)\n                .create();\n    }\n\n    private static int getErrorMessage(final int frameworkError) {\n        int messageId = R.string.fen__play_error_message;\n\n        if (frameworkError == MediaPlayer.MEDIA_ERROR_IO) {\n            Log.e(TAG, \"TextureVideoView error. File or network related operation errors.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_MALFORMED) {\n            Log.e(TAG, \"TextureVideoView error. Bitstream is not conforming to the related coding standard or file spec.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {\n            Log.e(TAG, \"TextureVideoView error. Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {\n            Log.e(TAG, \"TextureVideoView error. Some operation takes too long to complete, usually more than 3-5 seconds.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_UNKNOWN) {\n            Log.e(TAG, \"TextureVideoView error. Unspecified media player error.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {\n            Log.e(TAG, \"TextureVideoView error. Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature.\");\n        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {\n            Log.e(TAG, \"TextureVideoView error. The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file.\");\n            messageId = R.string.fen__play_progressive_error_message;\n        }\n        return messageId;\n    }\n\n    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {\n        @Override\n        public void onBufferingUpdate(final MediaPlayer mp, final int percent) {\n            mCurrentBufferPercentage = percent;\n        }\n    };\n\n    /**\n     * Register a callback to be invoked when the media file\n     * is loaded and ready to go.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnPreparedListener(final MediaPlayer.OnPreparedListener l) {\n        mOnPreparedListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when the end of a media file\n     * has been reached during playback.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnCompletionListener(final OnCompletionListener l) {\n        mOnCompletionListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when an error occurs\n     * during playback or setup.  If no listener is specified,\n     * or if the listener returned false, VideoView will inform\n     * the user of any errors.\n     *\n     * @param l The callback that will be run\n     */\n    public void setOnErrorListener(final OnErrorListener l) {\n        mOnErrorListener = l;\n    }\n\n    /**\n     * Register a callback to be invoked when an informational event\n     * occurs during playback or setup.\n     *\n     * @param l The callback that will be run\n     */\n    private void setOnInfoListener(final OnInfoListener l) {\n        mOnInfoListener = l;\n    }\n\n    private SurfaceTextureListener mSTListener = new SurfaceTextureListener() {\n        @Override\n        public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {\n            mSurfaceTexture = surface;\n            openVideo();\n        }\n\n        @Override\n        public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {\n            boolean isValidState = (mTargetState == STATE_PLAYING);\n            boolean hasValidSize = videoSizeCalculator.currentSizeIs(width, height);\n            if (mMediaPlayer != null && isValidState && hasValidSize) {\n                if (mSeekWhenPrepared != 0) {\n                    seekTo(mSeekWhenPrepared);\n                }\n                start();\n            }\n        }\n\n        @Override\n        public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {\n            mSurfaceTexture = null;\n            hideMediaController();\n            release(true);\n            return false;\n        }\n\n        @Override\n        public void onSurfaceTextureUpdated(final SurfaceTexture surface) {\n            mSurfaceTexture = surface;\n        }\n    };\n\n    /*\n     * release the media player in any state\n     */\n    private void release(final boolean clearTargetState) {\n        if (mMediaPlayer != null) {\n            mMediaPlayer.reset();\n            mMediaPlayer.release();\n            mMediaPlayer = null;\n            mCurrentState = STATE_IDLE;\n            if (clearTargetState) {\n                mTargetState = STATE_IDLE;\n            }\n        }\n    }\n\n    @Override\n    public boolean onTrackballEvent(final MotionEvent ev) {\n        if (isInPlaybackState() && fensterPlayerController != null) {\n            fensterPlayerController.show();\n        }\n        return false;\n    }\n\n    @Override\n    public boolean onKeyDown(final int keyCode, final KeyEvent event) {\n        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&\n                keyCode != KeyEvent.KEYCODE_VOLUME_UP &&\n                keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&\n                keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&\n                keyCode != KeyEvent.KEYCODE_MENU &&\n                keyCode != KeyEvent.KEYCODE_CALL &&\n                keyCode != KeyEvent.KEYCODE_ENDCALL;\n        if (isInPlaybackState() && isKeyCodeSupported && fensterPlayerController != null) {\n            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {\n                if (mMediaPlayer.isPlaying()) {\n                    pause();\n                    showMediaController();\n                } else {\n                    start();\n                    hideMediaController();\n                }\n                return true;\n            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {\n                if (!mMediaPlayer.isPlaying()) {\n                    start();\n                    hideMediaController();\n                }\n                return true;\n            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {\n                if (mMediaPlayer.isPlaying()) {\n                    pause();\n                    showMediaController();\n                }\n                return true;\n            } else {\n                fensterPlayerController.show();\n            }\n        }\n\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @Override\n    public void start() {\n        if (isInPlaybackState()) {\n            mMediaPlayer.start();\n            setKeepScreenOn(true);\n            mCurrentState = STATE_PLAYING;\n        }\n        mTargetState = STATE_PLAYING;\n    }\n\n    @Override\n    public void pause() {\n        if (isInPlaybackState()) {\n            if (mMediaPlayer.isPlaying()) {\n                mMediaPlayer.pause();\n                mCurrentState = STATE_PAUSED;\n                setKeepScreenOn(false);\n            }\n        }\n        mTargetState = STATE_PAUSED;\n    }\n\n    public void suspend() {\n        release(false);\n    }\n\n    public void resume() {\n        openVideo();\n    }\n\n    @Override\n    public int getDuration() {\n        if (isInPlaybackState()) {\n            return mMediaPlayer.getDuration();\n        }\n\n        return -1;\n    }\n\n    /**\n     * @return current position in milliseconds\n     */\n    @Override\n    public int getCurrentPosition() {\n        if (isInPlaybackState()) {\n            return mMediaPlayer.getCurrentPosition();\n        }\n        return 0;\n    }\n\n    public int getCurrentPositionInSeconds() {\n        return getCurrentPosition() / MILLIS_IN_SEC;\n    }\n\n    @Override\n    public void seekTo(final int millis) {\n        if (isInPlaybackState()) {\n            mMediaPlayer.seekTo(millis);\n            mSeekWhenPrepared = 0;\n        } else {\n            mSeekWhenPrepared = millis;\n        }\n    }\n\n    public void seekToSeconds(final int seconds) {\n        seekTo(seconds * MILLIS_IN_SEC);\n        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {\n            @Override\n            public void onSeekComplete(final MediaPlayer mp) {\n                Log.i(TAG, \"seek completed\");\n            }\n        });\n    }\n\n    @Override\n    public boolean isPlaying() {\n        return isInPlaybackState() && mMediaPlayer.isPlaying();\n    }\n\n    @Override\n    public int getBufferPercentage() {\n        if (mMediaPlayer != null) {\n            return mCurrentBufferPercentage;\n        }\n        return 0;\n    }\n\n    private boolean isInPlaybackState() {\n        return (mMediaPlayer != null &&\n                mCurrentState != STATE_ERROR &&\n                mCurrentState != STATE_IDLE &&\n                mCurrentState != STATE_PREPARING);\n    }\n\n    @Override\n    public boolean canPause() {\n        return mCanPause;\n    }\n\n    @Override\n    public boolean canSeekBackward() {\n        return mCanSeekBack;\n    }\n\n    @Override\n    public boolean canSeekForward() {\n        return mCanSeekForward;\n    }\n\n    @Override\n    public int getAudioSessionId() {\n        if (mAudioSession == 0) {\n            MediaPlayer foo = new MediaPlayer();\n            mAudioSession = foo.getAudioSessionId();\n            foo.release();\n        }\n        return mAudioSession;\n    }\n\n    private final OnInfoListener onInfoToPlayStateListener = new OnInfoListener() {\n\n        @Override\n        public boolean onInfo(final MediaPlayer mp, final int what, final int extra) {\n            if (noPlayStateListener()) {\n                return false;\n            }\n\n            if (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START == what) {\n                onPlayStateListener.onFirstVideoFrameRendered();\n                onPlayStateListener.onPlay();\n            }\n            if (MediaPlayer.MEDIA_INFO_BUFFERING_START == what) {\n                onPlayStateListener.onBuffer();\n            }\n            if (MediaPlayer.MEDIA_INFO_BUFFERING_END == what) {\n                onPlayStateListener.onPlay();\n            }\n\n            return false;\n        }\n    };\n\n    private boolean noPlayStateListener() {\n        return !hasPlayStateListener();\n    }\n\n    private boolean hasPlayStateListener() {\n        return onPlayStateListener != null;\n    }\n\n    public void setOnPlayStateListener(final FensterVideoStateListener onPlayStateListener) {\n        this.onPlayStateListener = onPlayStateListener;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/VideoSizeCalculator.java",
    "content": "package com.malmstein.fenster.view;\n\nimport android.view.SurfaceHolder;\nimport android.view.View;\n\npublic class VideoSizeCalculator {\n\n    private Dimens dimens;\n\n    private int mVideoWidth;\n    private int mVideoHeight;\n\n    public VideoSizeCalculator() {\n        dimens = new Dimens();\n    }\n\n    public void setVideoSize(int mVideoWidth, int mVideoHeight) {\n        this.mVideoWidth = mVideoWidth;\n        this.mVideoHeight = mVideoHeight;\n    }\n\n    public boolean hasASizeYet() {\n        return mVideoWidth > 0 && mVideoHeight > 0;\n    }\n\n    protected Dimens measure(int widthMeasureSpec, int heightMeasureSpec) {\n        int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);\n        int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);\n        if (hasASizeYet()) {\n\n            int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);\n            int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);\n            int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);\n            int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);\n\n            if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {\n                // the size is fixed\n                width = widthSpecSize;\n                height = heightSpecSize;\n\n                // for compatibility, we adjust size based on aspect ratio\n                if (mVideoWidth * height < width * mVideoHeight) {\n                    width = height * mVideoWidth / mVideoHeight;\n                } else if (mVideoWidth * height > width * mVideoHeight) {\n                    height = width * mVideoHeight / mVideoWidth;\n                }\n            } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {\n                // only the width is fixed, adjust the height to match aspect ratio if possible\n                width = widthSpecSize;\n                height = width * mVideoHeight / mVideoWidth;\n                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {\n                    // couldn't match aspect ratio within the constraints\n                    height = heightSpecSize;\n                }\n            } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {\n                // only the height is fixed, adjust the width to match aspect ratio if possible\n                height = heightSpecSize;\n                width = height * mVideoWidth / mVideoHeight;\n                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {\n                    // couldn't match aspect ratio within the constraints\n                    width = widthSpecSize;\n                }\n            } else {\n                // neither the width nor the height are fixed, try to use actual video size\n                width = mVideoWidth;\n                height = mVideoHeight;\n                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {\n                    // too tall, decrease both width and height\n                    height = heightSpecSize;\n                    width = height * mVideoWidth / mVideoHeight;\n                }\n                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {\n                    // too wide, decrease both width and height\n                    width = widthSpecSize;\n                    height = width * mVideoHeight / mVideoWidth;\n                }\n            }\n        }\n        dimens.width = width;\n        dimens.height = height;\n        return dimens;\n    }\n\n    public boolean currentSizeIs(int w, int h) {\n        return mVideoWidth == w && mVideoHeight == h;\n    }\n\n    public void updateHolder(SurfaceHolder holder) {\n        holder.setFixedSize(mVideoWidth, mVideoHeight);\n    }\n\n    static class Dimens {\n        int width;\n        int height;\n\n        public int getWidth() {\n            return width;\n        }\n\n        public int getHeight() {\n            return height;\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/res/layout/fen__fragment_fenster_gesture.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:padding=\"@dimen/fen__padding_medium\">\n\n  <com.malmstein.fenster.gestures.FensterGestureControllerView\n    android:id=\"@+id/fen__play_gesture_controller\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n\n  <SeekBar\n    android:id=\"@+id/fen__play_gesture_horizontal_seekbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_centerInParent=\"true\"\n    android:max=\"1000\"\n    android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"/>\n\n  <SeekBar\n    android:id=\"@+id/fen__play_gesture_vertical_seekbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"\n    android:max=\"1000\"\n    android:rotation=\"90\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "library/src/main/res/layout/fen__fragment_fenster_video.xml",
    "content": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\">\n\n  <com.malmstein.fenster.view.FensterVideoView\n    android:id=\"@+id/fen__play_video_texture\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:keepScreenOn=\"true\"\n    android:fitsSystemWindows=\"true\" />\n\n  <com.malmstein.fenster.controller.MediaFensterPlayerController\n    android:id=\"@+id/fen__play_video_controller\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\" />\n\n  <com.malmstein.fenster.view.FensterLoadingView\n    android:id=\"@+id/fen__play_video_loading\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "library/src/main/res/layout/fen__view_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ProgressBar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/fen__media_controller_loading_view\"\n  android:layout_width=\"wrap_content\"\n  android:layout_height=\"wrap_content\"\n  android:layout_gravity=\"center\"/>\n"
  },
  {
    "path": "library/src/main/res/layout/fen__view_media_controller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/media_controller_root\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:orientation=\"vertical\"\n  android:layout_gravity=\"bottom\"\n  android:fitsSystemWindows=\"true\"\n  android:gravity=\"bottom\">\n\n  <com.malmstein.fenster.gestures.FensterGestureControllerView\n    android:id=\"@+id/media_controller_gestures_area\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_above=\"@+id/media_controller_bottom_root\" />\n\n  <LinearLayout\n    android:id=\"@+id/media_controller_bottom_root\"\n    android:layout_alignParentBottom=\"true\"\n    android:visibility=\"gone\"\n    android:background=\"@color/fen__default_bg\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingTop=\"@dimen/fen__padding_small\"\n    android:paddingBottom=\"@dimen/fen__padding_small\"\n    android:paddingLeft=\"@dimen/fen__padding_small\"\n    android:paddingRight=\"@dimen/fen__padding_extra_large\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n      android:id=\"@+id/fen__media_controller_bottom_area\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:orientation=\"horizontal\"\n      android:gravity=\"center\">\n\n      <TextView\n        android:id=\"@+id/fen__media_controller_time_current\"\n        style=\"@style/MediaText\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n      <SeekBar\n        android:id=\"@+id/fen__media_controller_progress\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"\n        android:layout_weight=\"8\" />\n\n      <TextView\n        android:id=\"@+id/fen__media_controller_time\"\n        style=\"@style/MediaText\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n      android:id=\"@+id/fen__media_controller_controls\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:gravity=\"center\"\n      android:orientation=\"horizontal\">\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/fen__ic_action_bulb\"/>\n\n      <com.malmstein.fenster.seekbar.BrightnessSeekBar\n        android:id=\"@+id/fen__media_controller_brightness\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"\n        android:layout_weight=\"3\" />\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_previous\"\n        style=\"@style/MediaButton.Previous\" />\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_pause\"\n        style=\"@style/MediaButton.Play\" />\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_next\"\n        style=\"@style/MediaButton.Next\"\n        android:contentDescription=\"Next\" />\n\n      <com.malmstein.fenster.seekbar.VolumeSeekBar\n        android:id=\"@+id/fen__media_controller_volume\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"\n        android:layout_weight=\"3\" />\n\n      <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/fen__ic_action_music_2\" />\n\n    </LinearLayout>\n\n  </LinearLayout>\n\n</RelativeLayout>\n\n"
  },
  {
    "path": "library/src/main/res/layout/fen__view_simple_media_controller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.malmstein.fenster.view.FensterTouchRoot xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  android:id=\"@+id/media_controller_touch_root\"\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  android:background=\"@color/fen__default_bg\">\n\n  <RelativeLayout\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n      android:id=\"@+id/fen__media_controller_bottom_area\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignParentBottom=\"true\"\n      android:orientation=\"horizontal\"\n      android:layout_marginBottom=\"@dimen/fen__media_controller_bottom_margin\"\n      android:gravity=\"top\">\n\n      <TextView\n        android:id=\"@+id/fen__media_controller_time_current\"\n        style=\"@style/MediaText\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n      <SeekBar\n        android:id=\"@+id/fen__media_controller_progress\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"@dimen/fen__media_controller_seekbar_height\"\n        android:layout_weight=\"8\" />\n\n      <TextView\n        android:id=\"@+id/fen__media_controller_time\"\n        style=\"@style/MediaText\"\n        android:layout_width=\"0dip\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\" />\n\n    </LinearLayout>\n\n\n    <LinearLayout\n      android:id=\"@+id/media_controller_controls_root\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      android:layout_above=\"@+id/fen__media_controller_bottom_area\"\n      android:gravity=\"center\"\n      android:orientation=\"horizontal\">\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_previous\"\n        style=\"@style/MediaButton.Previous\"\n        android:contentDescription=\"Previous Piece\" />\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_pause\"\n        style=\"@style/MediaButton.Play\" />\n\n      <ImageButton\n        android:id=\"@+id/fen__media_controller_next\"\n        style=\"@style/MediaButton.Next\"\n        android:contentDescription=\"Next Piece\" />\n\n    </LinearLayout>\n\n    <ProgressBar\n      android:id=\"@+id/fen__media_controller_loading_view\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_alignTop=\"@id/media_controller_controls_root\"\n      android:layout_alignBottom=\"@id/media_controller_controls_root\"\n      android:layout_centerInParent=\"true\" />\n\n  </RelativeLayout>\n\n</com.malmstein.fenster.view.FensterTouchRoot>\n"
  },
  {
    "path": "library/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <attr name=\"scaleType\" format=\"enum\">\n    <enum name=\"scaleToFit\" value=\"0\" />\n    <enum name=\"crop\" value=\"1\" />\n  </attr>\n\n  <declare-styleable name=\"FensterVideoView\">\n    <attr name=\"scaleType\" />\n  </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values/fen__colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <color name=\"fen__default_bg\">#aa000000</color>\n\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values/fen__dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <!-- PADDINGS -->\n  <dimen name=\"fen__padding_none\">0dp</dimen>\n  <dimen name=\"fen__padding_small\">2dp</dimen>\n  <dimen name=\"fen__padding_medium\">4dp</dimen>\n  <dimen name=\"fen__padding_extra_large\">48dp</dimen>\n\n  <!-- MEDIA CONTROLLER -->\n  <dimen name=\"fen__media_controller_text_size\">14sp</dimen>\n  <dimen name=\"fen__media_controller_seekbar_width\">32dip</dimen>\n  <dimen name=\"fen__media_controller_seekbar_height\">32dip</dimen>\n  <dimen name=\"fen__media_controller_top_margin\">80dip</dimen>\n  <dimen name=\"fen__media_controller_bottom_margin\">12dip</dimen>\n\n  <dimen name=\"fen__media_controller_button_height\">52dip</dimen>\n  <dimen name=\"fen__media_controller_button_width\">71dip</dimen>\n\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values/fen__strings.xml",
    "content": "<resources>\n  <string name=\"fen__app_name\">Fenster</string>\n  <string name=\"fen__play_error_message\">Impossible to play the video.</string>\n  <string name=\"fen__play_progressive_error_message\">Impossible to play the progressive video.</string>\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values/fen__styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MediaButton\">\n    <item name=\"android:background\">@null</item>\n    <item name=\"android:layout_width\">@dimen/fen__media_controller_button_width</item>\n    <item name=\"android:layout_height\">@dimen/fen__media_controller_button_height</item>\n  </style>\n\n  <style name=\"MediaButton.Previous\">\n    <item name=\"android:src\">@android:drawable/ic_media_rew</item>\n  </style>\n\n  <style name=\"MediaButton.Play\">\n    <item name=\"android:src\">@android:drawable/ic_media_play</item>\n  </style>\n\n  <style name=\"MediaButton.Next\">\n    <item name=\"android:src\">@android:drawable/ic_media_ff</item>\n  </style>\n\n  <style name=\"MediaText\">\n    <item name=\"android:textSize\">@dimen/fen__media_controller_text_size</item>\n    <item name=\"android:textStyle\">bold</item>\n    <item name=\"android:paddingTop\">@dimen/fen__padding_medium</item>\n    <item name=\"android:textColor\">@android:color/white</item>\n    <item name=\"android:gravity\">center_horizontal</item>\n  </style>\n\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values/public.xml",
    "content": "<resources>\n  <public name='fen__app_name' type='string' />\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':library'\n"
  },
  {
    "path": "team-props/bintray.gradle",
    "content": "if (project.plugins.hasPlugin('com.android.library')) {\n    apply from: 'https://gist.githubusercontent.com/hal9002/691bbf3340dde3a4591d/raw' // Android plugin\n} else {\n    apply from: 'https://gist.githubusercontent.com/hal9002/810b1d3e841fd6160c5d/raw' // Java plugin\n}\n\napply plugin: 'com.jfrog.bintray'\n\ndef localReleaseDest = \"${buildDir}/release\"\n\ndef getString(String propertyName) {\n    project.hasProperty(propertyName) ? project.getProperty(propertyName) : ''\n}\n\ndef getBoolean(String propertyName) {\n    project.hasProperty(propertyName) ? Boolean.valueOf(project.getProperty(propertyName)) : true\n}\n\nbintray {\n    user = getString('BINTRAY_USER')\n    key = getString('BINTRAY_KEY')\n    publish = getBoolean('UPLOAD_AUTO_PUBLISH')\n\n    filesSpec {\n        from localReleaseDest\n        into \".\"\n        exclude '**/maven-metadata.*'\n    }\n\n    pkg {\n        repo = 'maven'\n        userOrg = 'novoda'\n        name = getString('UPLOAD_NAME')\n        desc = getString('UPLOAD_DESCRIPTION')\n        websiteUrl = getString('UPLOAD_WEBSITE')\n        issueTrackerUrl = getString('UPLOAD_ISSUE_TRACKER')\n        vcsUrl = getString('UPLOAD_REPOSITORY')\n\n        licenses = ['Apache-2.0']\n        version {\n            name = getString('UPLOAD_VERSION')\n        }\n    }\n}\n\ntask publishReleaseToBintray(dependsOn: ['generateRelease', 'bintrayUpload'])\nbintrayUpload.mustRunAfter generateRelease\npublishReleaseToBintray.mustRunAfter bintrayUpload\n\ngradle.taskGraph.useFilter { task ->\n    shouldBeExecuted(task)\n}\n\ndef shouldBeExecuted(def task) {\n    !isBintrayRelated(task) || shouldPublishToBintray()\n}\n\ndef isBintrayRelated(def task) {\n    task.name.toLowerCase().contains('bintray')\n}\n\ndef shouldPublishToBintray() {\n    getBoolean('MANUAL_PUBLISH')\n}"
  },
  {
    "path": "team-props/deploy.gradle",
    "content": "apply plugin: 'maven'\n\ndef groupId = project.PUBLISH_GROUP_ID\ndef artifactId = project.PUBLISH_ARTIFACT_ID\ndef version = project.PUBLISH_VERSION\n\ndef localReleaseDest = \"${buildDir}/release\"\n\ntask androidJavadocs(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\n\ntask androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n    classifier = 'javadoc'\n    from androidJavadocs.destinationDir\n}\n\ntask androidSourcesJar(type: Jar) {\n    classifier = 'sources'\n    from android.sourceSets.main.java.srcDirs\n}\n\nuploadArchives {\n    repositories.mavenDeployer {\n        pom.groupId = groupId\n        pom.artifactId = artifactId\n        pom.version = version\n        // Add other pom properties here if you want (developer details / licenses)\n        repository(url: \"file://${localReleaseDest}\")\n    }\n}\n\ntask generateRelease << {\n    println \"Release ${version} can be found at ${localReleaseDest}/\"\n}\ngenerateRelease.dependsOn build\ngenerateRelease.dependsOn uploadArchives\n\nartifacts {\n    archives androidSourcesJar\n    archives androidJavadocsJar\n}"
  }
]