Full Code of malmstein/fenster for AI

master cd3d32a28112 cached
67 files
148.0 KB
35.4k tokens
310 symbols
1 requests
Download .txt
Repository: malmstein/fenster
Branch: master
Commit: cd3d32a28112
Files: 67
Total size: 148.0 KB

Directory structure:
gitextract_0wxaa4k2/

├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── malmstein/
│       │               └── fenster/
│       │                   └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── malmstein/
│           │           └── fenster/
│           │               └── demo/
│           │                   ├── GestureMediaPlayerActivity.java
│           │                   ├── MediaPlayerDemoActivity.java
│           │                   ├── ScaleMediaPlayerActivity.java
│           │                   └── SimpleMediaPlayerActivity.java
│           └── res/
│               ├── layout/
│               │   ├── activity_demo.xml
│               │   ├── activity_gesture_media_player.xml
│               │   ├── activity_scale_media_player.xml
│               │   └── activity_simple_media_player.xml
│               ├── menu/
│               │   └── demo_menu.xml
│               ├── values/
│               │   ├── attrs.xml
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── publish.gradle
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── malmstein/
│       │               └── fenster/
│       │                   └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── malmstein/
│           │           └── fenster/
│           │               ├── controller/
│           │               │   ├── FensterPlayerController.java
│           │               │   ├── FensterPlayerControllerVisibilityListener.java
│           │               │   ├── MediaFensterPlayerController.java
│           │               │   └── SimpleMediaFensterPlayerController.java
│           │               ├── gestures/
│           │               │   ├── FensterEventsListener.java
│           │               │   ├── FensterGestureControllerView.java
│           │               │   └── FensterGestureListener.java
│           │               ├── helper/
│           │               │   └── BrightnessHelper.java
│           │               ├── play/
│           │               │   ├── FensterPlayer.java
│           │               │   ├── FensterVideoFragment.java
│           │               │   └── FensterVideoStateListener.java
│           │               ├── seekbar/
│           │               │   ├── BrightnessSeekBar.java
│           │               │   └── VolumeSeekBar.java
│           │               └── view/
│           │                   ├── FensterLoadingView.java
│           │                   ├── FensterTouchRoot.java
│           │                   ├── FensterVideoView.java
│           │                   └── VideoSizeCalculator.java
│           └── res/
│               ├── layout/
│               │   ├── fen__fragment_fenster_gesture.xml
│               │   ├── fen__fragment_fenster_video.xml
│               │   ├── fen__view_loading.xml
│               │   ├── fen__view_media_controller.xml
│               │   └── fen__view_simple_media_controller.xml
│               └── values/
│                   ├── attrs.xml
│                   ├── fen__colors.xml
│                   ├── fen__dimens.xml
│                   ├── fen__strings.xml
│                   ├── fen__styles.xml
│                   └── public.xml
├── settings.gradle
└── team-props/
    ├── bintray.gradle
    └── deploy.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/
out/
build.log
build-log.xml

# Local configuration file (sdk path, etc)
local.properties

.project
.classpath
.settings
target/
build/
classes/
gen-external-apklibs/
tmp/

# IDEA Ignores
*.iml
*.ipr
*.iws
.idea/
.gradle/

# Android
local.properties

# MAC
*.DS_Store

# api scripts
session.key
access.token
response.txt
.pt
*.orig
terminator/

=======

app/build
app-test/build
lint-results.xml


================================================
FILE: .travis.yml
================================================
language: android

android:
  components:
    - build-tools-23.0.1
    - android-23
    - extra-android-support
    - extra-google-m2repository
    - extra-android-m2repository

script:
  - ./gradlew clean build


================================================
FILE: LICENSE.txt
================================================
     Copyright 2014 David Gonzalez

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


				                Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

================================================
FILE: README.md
================================================
Fenster
=============================

A 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/

![Demo gif](https://raw.githubusercontent.com/malmstein/Fenster/master/art/video_example.gif)

Install
=============================

[ ![Download](https://api.bintray.com/packages/malmstein/maven/fenster/images/download.svg) ](https://bintray.com/malmstein/maven/fenster/_latestVersion)

To get the current snapshot version:

```groovy
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.malmstein:fenster:0.0.2'
    }
}
```

### minSDK

The minSDK for the use of the library is minSDK 16

Displaying a video with custom controller
=============================

### Add a TextureVideoView and a PlayerController to your Activity or Fragment

```xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/default_bg"
  tools:context=".DemoActivity">

  <com.malmstein.fenster.view.FensterVideoView
    android:id="@+id/play_video_texture"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    android:fitsSystemWindows="true" />

  <com.malmstein.fenster.controller.MediaFensterPlayerController
    android:id="@+id/play_video_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:animateLayoutChanges="true"
    android:fitsSystemWindows="true" />

</FrameLayout>
```

### Setting video URL

In order to display a video, simply set the video URL and call start. **You can also start the video from a desired second too**.


```java
@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);

    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);
    playerController = (PlayerController) findViewById(R.id.play_video_controller);

    textureView.setMediaController(playerController);

    textureView.setVideo("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
                PlayerController.DEFAULT_VIDEO_START);
    textureView.start();
}
```

### Exposed listeners

By default there are the exposed listeners. The `NavigationListener` will listen to the to **Previous** and **Next** events triggered
by the controller. The `VisibilityListener` will be triggered when the `PlayerController` visibility changes.

```java
playerController.setNavigationListener(this);
playerController.setVisibilityListener(this);
```

Using the Gesture Detection Player Controller
=============================

### Attach a listener to your player controller

As described in this blog post http://www.malmstein.com/how-to-use-a-textureview-to-display-a-video-with-custom-media-player-controls/
it's very simple to use. Just add a listener to Player Controller

```java
playerController.setFensterEventsListener(this);
```

The Fenster Events Listener allows you to react to the gestures

```java
public interface FensterEventsListener {

    void onTap();

    void onHorizontalScroll(MotionEvent event, float delta);

    void onVerticalScroll(MotionEvent event, float delta);

    void onSwipeRight();

    void onSwipeLeft();

    void onSwipeBottom();

    void onSwipeTop();
}
```

### Use MediaPlayerController instead of SimpleMediaPlayerController

MediaFensterPlayerController also shows volume and brightness controls, if you just want to use a simple media controller
then the recommendation is to use SimpleMediaFensterPlayerController

```xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/default_bg"
  tools:context=".DemoActivity">

  <com.malmstein.fenster.view.FensterVideoView
    android:id="@+id/play_video_texture"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    android:fitsSystemWindows="true" />

  <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController
    android:id="@+id/play_video_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:animateLayoutChanges="true"
    android:fitsSystemWindows="true" />

</FrameLayout>
```

### Using the Volume and Brightness Seekbar

Add them to your layout:

```xml
  <com.malmstein.fenster.seekbar.BrightnessSeekBar
    android:id="@+id/media_controller_volume"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
```

```xml
  <com.malmstein.fenster.seekbar.VolumeSeekBar
    android:id="@+id/media_controller_volume"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
```

Initialise them from your Fragment or Activity:

```java
mVolume = (VolumeSeekBar) findViewById(R.id.media_controller_volume);
mVolume.initialise(this);

mBrightness = (BrightnessSeekBar) findViewById(R.id.media_controller_brightness);
mBrightness.initialise(this);

```

You'll get a callback when the seekbar is being dragged:

```java
@Override
public void onVolumeStartedDragging() {
    mDragging = true;
}

@Override
public void onVolumeFinishedDragging() {
    mDragging = false;
}

@Override
public void onBrigthnessStartedDragging() {
    mDragging = true;
}

@Override
public void onBrightnessFinishedDragging() {
    mDragging = false;
}
```

Support for different video origins
=============================

The `setVideo()` method allows you to load remote or local video files. You can also set the start time of the video 
(useful if you want to resume content), passing in a integer which corresponds to Milliseconds.

### Loading a remote stream


```java
@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);

    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);
    playerController = (PlayerController) findViewById(R.id.play_video_controller);

    textureView.setMediaController(playerController);

    textureView.setVideo("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
    textureView.start();
}
```

### Loading a local stream

`Fenster` uses the [AssetFileDescriptor](http://developer.android.com/intl/es/reference/android/content/res/AssetFileDescriptor.html) in 
order to load a local video stream.


```java
@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);

    textureView = (TextureVideoView) findViewById(R.id.play_video_texture);
    playerController = (PlayerController) findViewById(R.id.play_video_controller);

    textureView.setMediaController(playerController);
    
    AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.raw.big_buck_bunny);
    textureView.setVideo(assetFileDescriptor);
   
    textureView.start();
}
```


Support for video scaling modes
=============================

Sets video scaling mode. To make the target video scaling mode effective during playback, 
the default video scaling mode is VIDEO_SCALING_MODE_SCALE_TO_FIT. Uses [setVideoScalingMode](http://developer.android.com/intl/es/reference/android/media/MediaPlayer.html)

There are two different video scaling modes: `scaleToFit` and `crop`

In order to use it, `Fenster` allows you to pass in an argument from the xml layout:

```xml
<com.malmstein.fenster.view.FensterVideoView
  android:id="@+id/play_video_texture"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:scaleType="crop" />
```

  
License
-------

    (c) Copyright 2016 David Gonzalez

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
    }
}

repositories {
    jcenter()
}

apply plugin: 'com.android.application'

dependencies {
    compile project(":library")
//    compile 'com.malmstein:fenster:0.0.1'
}

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.malmstein.fenster.demo"
        minSdkVersion 16
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: app/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java
================================================
package com.malmstein.fenster;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.malmstein.fenster.demo">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.WRITE_SETTINGS" />

  <application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/FensterTheme">

    <activity
      android:name=".MediaPlayerDemoActivity"
      android:label="@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>

    <activity
      android:name=".SimpleMediaPlayerActivity"
      android:configChanges="orientation|keyboardHidden|screenSize"
      android:label="@string/app_name"
      android:screenOrientation="sensorLandscape"
      android:theme="@style/FensterFullscreenTheme" />

    <activity
      android:name=".ScaleMediaPlayerActivity"
      android:configChanges="orientation|keyboardHidden|screenSize"
      android:label="@string/app_name"
      android:screenOrientation="sensorLandscape" />

    <activity
      android:name=".GestureMediaPlayerActivity"
      android:label="@string/app_name"
      android:screenOrientation="sensorLandscape" />

  </application>

</manifest>


================================================
FILE: app/src/main/java/com/malmstein/fenster/demo/GestureMediaPlayerActivity.java
================================================
package com.malmstein.fenster.demo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

import com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;
import com.malmstein.fenster.play.FensterVideoFragment;

public class GestureMediaPlayerActivity extends Activity implements FensterPlayerControllerVisibilityListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gesture_media_player);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        initVideo();
    }

    private void initVideo(){
        findVideoFragment().setVisibilityListener(this);
        findVideoFragment().playExampleVideo();
    }

    private FensterVideoFragment findVideoFragment(){
        return (FensterVideoFragment) getFragmentManager().findFragmentById(R.id.play_demo_fragment);
    }

    @Override
    public void onControlsVisibilityChange(boolean value) {
        setSystemUiVisibility(value);
    }

    private void setSystemUiVisibility(final boolean visible) {
        int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;

        if (!visible) {
            newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
        }

        final View decorView = getWindow().getDecorView();
        decorView.setSystemUiVisibility(newVis);
        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(final int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {
                    findVideoFragment().showFensterController();
                }
            }
        });
    }
}


================================================
FILE: app/src/main/java/com/malmstein/fenster/demo/MediaPlayerDemoActivity.java
================================================
package com.malmstein.fenster.demo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MediaPlayerDemoActivity extends Activity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_demo);
        bindViews();
    }

    private void bindViews() {
        findViewById(R.id.start_gesture_media_player_button).setOnClickListener(this);
        findViewById(R.id.start_simple_media_player_button).setOnClickListener(this);
        findViewById(R.id.local_file_media_player_button).setOnClickListener(this);
        findViewById(R.id.start_scale_media_player_button).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_gesture_media_player_button:
                startActivity(new Intent(this, GestureMediaPlayerActivity.class));
                break;
            case R.id.start_simple_media_player_button:
                startActivity(new Intent(this, SimpleMediaPlayerActivity.class));
                break;
            case R.id.local_file_media_player_button:
                Intent localStream = new Intent(this, SimpleMediaPlayerActivity.class);
                localStream.putExtra(SimpleMediaPlayerActivity.KEY_LOCAL_FILE, true);
                startActivity(localStream);
                break;
            case R.id.start_scale_media_player_button:
                startActivity(new Intent(this, ScaleMediaPlayerActivity.class));
                break;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.demo_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_about) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}


================================================
FILE: app/src/main/java/com/malmstein/fenster/demo/ScaleMediaPlayerActivity.java
================================================
package com.malmstein.fenster.demo;

import android.app.Activity;
import android.os.Bundle;

import com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;
import com.malmstein.fenster.view.FensterVideoView;

public class ScaleMediaPlayerActivity extends Activity {

    private FensterVideoView textureView;
    private SimpleMediaFensterPlayerController fullScreenMediaPlayerController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_scale_media_player);

        bindViews();
        initVideo();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        textureView.setVideo("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
        textureView.start();
    }

    private void bindViews() {
        textureView = (FensterVideoView) findViewById(R.id.play_video_texture);
        fullScreenMediaPlayerController = (SimpleMediaFensterPlayerController) findViewById(R.id.play_video_controller);
    }

    private void initVideo() {
        textureView.setMediaController(fullScreenMediaPlayerController);
        textureView.setOnPlayStateListener(fullScreenMediaPlayerController);
    }

}


================================================
FILE: app/src/main/java/com/malmstein/fenster/demo/SimpleMediaPlayerActivity.java
================================================
package com.malmstein.fenster.demo;

import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.os.Bundle;
import android.view.View;

import com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;
import com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;
import com.malmstein.fenster.view.FensterVideoView;

public class SimpleMediaPlayerActivity extends Activity implements FensterPlayerControllerVisibilityListener {

    public static final String KEY_LOCAL_FILE = BuildConfig.APPLICATION_ID + "KEY_LOCAL_FILE";
    private FensterVideoView textureView;
    private SimpleMediaFensterPlayerController fullScreenMediaPlayerController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_simple_media_player);

        bindViews();
        initVideo();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        if (getIntent().hasExtra(KEY_LOCAL_FILE)) {
            AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.raw.big_buck_bunny);
            textureView.setVideo(assetFileDescriptor);
        } else {
            textureView.setVideo("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
        }

        textureView.start();
    }

    private void bindViews() {
        textureView = (FensterVideoView) findViewById(R.id.play_video_texture);
        fullScreenMediaPlayerController = (SimpleMediaFensterPlayerController) findViewById(R.id.play_video_controller);
    }

    private void initVideo() {
        fullScreenMediaPlayerController.setVisibilityListener(this);
        textureView.setMediaController(fullScreenMediaPlayerController);
        textureView.setOnPlayStateListener(fullScreenMediaPlayerController);
    }

    private void setSystemUiVisibility(final boolean visible) {
        int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;

        if (!visible) {
            newVis |= View.SYSTEM_UI_FLAG_LOW_PROFILE
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
        }

        final View decorView = getWindow().getDecorView();
        decorView.setSystemUiVisibility(newVis);
        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(final int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {
                    fullScreenMediaPlayerController.show();
                }
            }
        });
    }

    @Override
    public void onControlsVisibilityChange(final boolean value) {
        setSystemUiVisibility(value);
    }
}


================================================
FILE: app/src/main/res/layout/activity_demo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:padding="@dimen/activity_horizontal_margin">

  <TextView
    android:id="@+id/demo_activity_controller_description"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="@string/description_demo_activity_controller" />

  <Button
    android:id="@+id/start_simple_media_player_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:text="@string/title_simple_media_player" />

  <Button
    android:id="@+id/start_scale_media_player_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/title_scale_media_player" />

  <Button
    android:id="@+id/start_gesture_media_player_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/title_gesture_media_player" />

  <TextView
    android:id="@+id/demo_activity_origin_description"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:gravity="center"
    android:text="@string/description_demo_activity_origin" />

  <Button
    android:id="@+id/local_file_media_player_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:text="@string/title_local_media_player" />

</LinearLayout>


================================================
FILE: app/src/main/res/layout/activity_gesture_media_player.xml
================================================
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/play_demo_fragment"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  class="com.malmstein.fenster.play.FensterVideoFragment"/>



================================================
FILE: app/src/main/res/layout/activity_scale_media_player.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/default_bg"
  android:gravity="center"
  tools:context=".SimpleMediaPlayerActivity">

  <RelativeLayout
    android:layout_width="300dp"
    android:layout_height="300dp">

    <com.malmstein.fenster.view.FensterVideoView
      android:id="@+id/play_video_texture"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:scaleType="crop" />

    <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController
      android:id="@+id/play_video_controller"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:animateLayoutChanges="true" />

  </RelativeLayout>


</LinearLayout>


================================================
FILE: app/src/main/res/layout/activity_simple_media_player.xml
================================================
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/default_bg"
  tools:context=".SimpleMediaPlayerActivity">

  <com.malmstein.fenster.view.FensterVideoView
    android:id="@+id/play_video_texture"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    android:fitsSystemWindows="true" />

  <com.malmstein.fenster.controller.SimpleMediaFensterPlayerController
    android:id="@+id/play_video_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:animateLayoutChanges="true"
    android:fitsSystemWindows="true" />

</RelativeLayout>


================================================
FILE: app/src/main/res/menu/demo_menu.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  tools:context="com.malmstein.fenster.demo.MediaPlayerDemoActivity">
  <item
    android:id="@+id/action_about"
    android:title="@string/action_about"
    android:orderInCategory="100"
    android:showAsAction="never" />
</menu>


================================================
FILE: app/src/main/res/values/attrs.xml
================================================
<resources>

  <declare-styleable name="ButtonBarContainerTheme">
    <attr name="metaButtonBarStyle" format="reference" />
    <attr name="metaButtonBarButtonStyle" format="reference" />
  </declare-styleable>

</resources>


================================================
FILE: app/src/main/res/values/colors.xml
================================================
<resources>

    <color name="black_overlay">#66000000</color>
    <color name="default_bg">#aa000000</color>

</resources>


================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
  <dimen name="activity_horizontal_margin">16dp</dimen>
  <dimen name="activity_vertical_margin">16dp</dimen>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

  <string name="app_name">Fenster</string>
  <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>
  <string name="description_demo_activity_origin">Alternatively, you can also load a file from the Raw folder</string>

  <string name="action_about">About</string>

  <string name="title_simple_media_player">Simple Media Player Controller</string>
  <string name="title_scale_media_player">Different Media Player Scale Type</string>
  <string name="title_gesture_media_player">Gesture Media Player Controller</string>
  <string name="title_local_media_player">Load file from local Raw folder using an AssetFileDescriptor</string>

</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>


  <style name="FensterTheme" parent="android:Theme.Holo.Light.DarkActionBar">

  </style>

  <style name="FensterFullscreenTheme" parent="android:Theme.Holo">
    <item name="android:actionBarStyle">@style/FensterFullscreenActionBarStyle</item>
    <item name="android:windowActionBarOverlay">true</item>
    <item name="android:windowBackground">@null</item>
    <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
    <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
  </style>

  <style name="FensterFullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
    <item name="android:background">@color/default_bg</item>
  </style>

</resources>


================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
<resources>
  <dimen name="activity_horizontal_margin">64dp</dimen>
</resources>


================================================
FILE: build.gradle
================================================
buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
    }

}

allprojects {
    buildscript {
        repositories {
            jcenter()
        }
    }

    repositories {
        jcenter()
    }
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Fri Aug 28 15:24:47 BST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: library/.gitignore
================================================
/build


================================================
FILE: library/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'com.novoda.bintray-release'

def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def buildTime = new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone('UTC'))

// Manifest version information!
int versionMajor = 0
int versionMinor = 0
int versionPatch = 2
int versionBuild = 1 // bump for dogfood builds, public betas, etc.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        classpath 'com.novoda:bintray-release:0.3.4'
    }
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        versionCode versionMajor * 100**3 + versionMinor * 100**2 + versionPatch * 100 + versionBuild
        versionName "${versionMajor}.${versionMinor}.${versionPatch}"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }

    resourcePrefix 'fen__'
}

publish {
    userOrg = 'malmstein'
    groupId = 'com.malmstein'
    artifactId = 'fenster'
    publishVersion = '0.0.2'
    desc = 'A library to display videos in a TextureView using a custom MediaPlayer controller'
    website = 'https://github.com/malmstein/fenster'
}


================================================
FILE: library/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: library/publish.gradle
================================================
ext {
    PUBLISH_GROUP_ID = 'com.malmstein'
    PUBLISH_ARTIFACT_ID = 'fenster'
    PUBLISH_VERSION = project.version

    UPLOAD_NAME = PUBLISH_ARTIFACT_ID
    UPLOAD_DESCRIPTION = 'A library to display videos in a TextureView using a custom MediaPlayer controller'
    UPLOAD_WEBSITE = 'https://github.com/malmstein/fenster'
    UPLOAD_ISSUE_TRACKER = "${UPLOAD_WEBSITE}/issues"
    UPLOAD_REPOSITORY = "${UPLOAD_WEBSITE}.git"
    UPLOAD_VERSION = PUBLISH_VERSION
}

apply from: 'team-props/bintray.gradle'

================================================
FILE: library/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java
================================================
package com.malmstein.fenster;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}

================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.malmstein.fenster">


</manifest>


================================================
FILE: library/src/main/java/com/malmstein/fenster/controller/FensterPlayerController.java
================================================
package com.malmstein.fenster.controller;

import com.malmstein.fenster.play.FensterPlayer;

public interface FensterPlayerController {

    void setMediaPlayer(FensterPlayer fensterPlayer);

    void setEnabled(boolean value);

    void show(int timeInMilliSeconds);

    void show();

    void hide();

    void setVisibilityListener(FensterPlayerControllerVisibilityListener visibilityListener);

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/controller/FensterPlayerControllerVisibilityListener.java
================================================
package com.malmstein.fenster.controller;

/**
 * Called to notify that the control have been made visible or hidden.
 * Implementation might want to show/hide actionbar or do other ui adjustments.
 * <p/>
 * Implementation must be provided via the corresponding setter.
 */
public interface FensterPlayerControllerVisibilityListener {

    void onControlsVisibilityChange(boolean value);

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/controller/MediaFensterPlayerController.java
================================================
package com.malmstein.fenster.controller;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import com.malmstein.fenster.R;
import com.malmstein.fenster.gestures.FensterEventsListener;
import com.malmstein.fenster.gestures.FensterGestureControllerView;
import com.malmstein.fenster.play.FensterPlayer;
import com.malmstein.fenster.seekbar.BrightnessSeekBar;
import com.malmstein.fenster.seekbar.VolumeSeekBar;

import java.util.Formatter;
import java.util.Locale;

/**
 * Controller to manage syncing the ui models with the UI Controls and MediaPlayer.
 * <p/>
 * Note that the ui models have a narrow scope (i.e. chapter list, piece navigation),
 * their interaction is orchestrated by this controller.ø
 * <p/>
 * It's actually a view currently, as is the android MediaController.
 * (which is a bit odd and should be subject to change.)
 */
public final class MediaFensterPlayerController extends RelativeLayout implements FensterPlayerController, FensterEventsListener, VolumeSeekBar.Listener, BrightnessSeekBar.Listener {

    /**
     * Called to notify that the control have been made visible or hidden.
     * Implementation might want to show/hide actionbar or do other ui adjustments.
     * <p/>
     * Implementation must be provided via the corresponding setter.
     */

    public static final String TAG = "PlayerController";

    public static final int DEFAULT_VIDEO_START = 0;
    public static final int ONE_FINGER = 1;
    public static final int MAX_VIDEO_PROGRESS = 1000;
    public static final int SKIP_VIDEO_PROGRESS = MAX_VIDEO_PROGRESS / 10;
    private static final int DEFAULT_TIMEOUT = 5000;
    private final OnClickListener mPauseListener = new OnClickListener() {
        public void onClick(final View v) {
            doPauseResume();
            show(DEFAULT_TIMEOUT);
        }
    };
    private static final int FADE_OUT = 1;
    private static final int SHOW_PROGRESS = 2;
    private FensterPlayerControllerVisibilityListener visibilityListener;
    private FensterPlayer mFensterPlayer;
    private boolean mShowing;
    private boolean mDragging;
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(final Message msg) {
            int pos;
            switch (msg.what) {
                case FADE_OUT:
                    if (mFensterPlayer.isPlaying()) {
                        hide();
                    } else {
                        // re-schedule to check again
                        Message fadeMessage = obtainMessage(FADE_OUT);
                        removeMessages(FADE_OUT);
                        sendMessageDelayed(fadeMessage, DEFAULT_TIMEOUT);
                    }
                    break;
                case SHOW_PROGRESS:
                    pos = setProgress();
                    if (!mDragging && mShowing && mFensterPlayer.isPlaying()) {
                        final Message message = obtainMessage(SHOW_PROGRESS);
                        sendMessageDelayed(message, 1000 - (pos % 1000));
                    }
                    break;
            }
        }
    };
    private boolean mManualDragging;
    private boolean mFirstTimeLoading = true;
    private StringBuilder mFormatBuilder;
    private Formatter mFormatter;
    private FensterGestureControllerView gestureControllerView;
    private View bottomControlsArea;
    private SeekBar mProgress;
    private BrightnessSeekBar mBrightness;
    private VolumeSeekBar mVolume;
    private TextView mEndTime;
    private TextView mCurrentTime;
    // There are two scenarios that can trigger the seekbar listener to trigger:
    //
    // The first is the user using the touchpad to adjust the posititon of the
    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
    // We're setting the field "mDragging" to true for the duration of the dragging
    // session to avoid jumps in the position in case of ongoing playback.
    //
    // The second scenario involves the user operating the scroll ball, in this
    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
    // we will simply apply the updated position without suspending regular updates.
    private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
        public void onStartTrackingTouch(final SeekBar bar) {
            show(3600000);

            mDragging = true;

            // By removing these pending progress messages we make sure
            // that a) we won't update the progress while the user adjusts
            // the seekbar and b) once the user is done dragging the thumb
            // we will post one of these messages to the queue again and
            // this ensures that there will be exactly one message queued up.
            mHandler.removeMessages(SHOW_PROGRESS);
        }

        public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {
            if (!fromuser && !mManualDragging) {
                // We're not interested in programmatically generated changes to
                // the progress bar's position.
                return;
            }

            long duration = mFensterPlayer.getDuration();
            long newposition = (duration * progress) / 1000L;
            mFensterPlayer.seekTo((int) newposition);
            if (mCurrentTime != null) {
                mCurrentTime.setText(stringForTime((int) newposition));
            }
        }

        public void onStopTrackingTouch(final SeekBar bar) {
            mDragging = false;
            setProgress();
            updatePausePlay();
            show(DEFAULT_TIMEOUT);

            // Ensure that progress is properly updated in the future,
            // the call to show() does not guarantee this because it is a
            // no-op if we are already showing.
            mHandler.sendEmptyMessage(SHOW_PROGRESS);
        }
    };
    private ImageButton mPauseButton;
    private ImageButton mNextButton;
    private ImageButton mPrevButton;
    private int lastPlayedSeconds = -1;

    public MediaFensterPlayerController(final Context context) {
        this(context, null);
    }

    public MediaFensterPlayerController(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MediaFensterPlayerController(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onFinishInflate() {
        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_media_controller, this);
        initControllerView();
    }

    @Override
    public void setMediaPlayer(final FensterPlayer fensterPlayer) {
        mFensterPlayer = fensterPlayer;
        updatePausePlay();
    }

    public void setVisibilityListener(final FensterPlayerControllerVisibilityListener visibilityListener) {
        this.visibilityListener = visibilityListener;
    }

    private void initControllerView() {
        bottomControlsArea = findViewById(R.id.media_controller_bottom_root);

        gestureControllerView = (FensterGestureControllerView) findViewById(R.id.media_controller_gestures_area);
        gestureControllerView.setFensterEventsListener(this);

        mPauseButton = (ImageButton) findViewById(R.id.fen__media_controller_pause);
        mPauseButton.requestFocus();
        mPauseButton.setOnClickListener(mPauseListener);

        mNextButton = (ImageButton) findViewById(R.id.fen__media_controller_next);
        mPrevButton = (ImageButton) findViewById(R.id.fen__media_controller_previous);

        mProgress = (SeekBar) findViewById(R.id.fen__media_controller_progress);
        mProgress.setOnSeekBarChangeListener(mSeekListener);
        mProgress.setMax(MAX_VIDEO_PROGRESS);

        mVolume = (VolumeSeekBar) findViewById(R.id.fen__media_controller_volume);
        mVolume.initialise(this);

        mBrightness = (BrightnessSeekBar) findViewById(R.id.fen__media_controller_brightness);
        mBrightness.initialise(this);

        mEndTime = (TextView) findViewById(R.id.fen__media_controller_time);
        mCurrentTime = (TextView) findViewById(R.id.fen__media_controller_time_current);
        mFormatBuilder = new StringBuilder();
        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
    }

    /**
     * Show the controller on screen. It will go away
     * automatically after the default time of inactivity.
     */
    @Override
    public void show() {
        show(DEFAULT_TIMEOUT);
    }

    /**
     * Show the controller on screen. It will go away
     * automatically after 'timeout' milliseconds of inactivity.
     *
     * @param timeInMilliSeconds The timeout in milliseconds. Use 0 to show
     *                           the controller until hide() is called.
     */
    @Override
    public void show(final int timeInMilliSeconds) {
        if (!mShowing) {
            showBottomArea();
            setProgress();
            if (mPauseButton != null) {
                mPauseButton.requestFocus();
            }
            mShowing = true;
            setVisibility(View.VISIBLE);
        }

        updatePausePlay();

        // cause the progress bar to be updated even if mShowing
        // was already true.  This happens, for example, if we're
        // paused with the progress bar showing the user hits play.
        mHandler.sendEmptyMessage(SHOW_PROGRESS);

        Message msg = mHandler.obtainMessage(FADE_OUT);
        if (timeInMilliSeconds != 0) {
            mHandler.removeMessages(FADE_OUT);
            mHandler.sendMessageDelayed(msg, timeInMilliSeconds);
        }

        if (visibilityListener != null) {
            visibilityListener.onControlsVisibilityChange(true);
        }

    }

    private void showBottomArea() {
        bottomControlsArea.setVisibility(View.VISIBLE);
    }

    public boolean isShowing() {
        return mShowing;
    }

    public boolean isFirstTimeLoading() {
        return mFirstTimeLoading;
    }

    /**
     * Remove the controller from the screen.
     */
    @Override
    public void hide() {
        if (!mDragging) {
            if (mShowing) {
                try {
                    mHandler.removeMessages(SHOW_PROGRESS);
                    setVisibility(View.INVISIBLE);
                } catch (final IllegalArgumentException ex) {
                    Log.w("MediaController", "already removed");
                }
                mShowing = false;
            }
            if (visibilityListener != null) {
                visibilityListener.onControlsVisibilityChange(false);
            }
        }

    }

    private String stringForTime(final int timeMs) {
        int totalSeconds = timeMs / 1000;

        int seconds = totalSeconds % 60;
        int minutes = (totalSeconds / 60) % 60;
        int hours = totalSeconds / 3600;

        mFormatBuilder.setLength(0);
        if (hours > 0) {
            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
        } else {
            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
        }
    }

    private int setProgress() {
        if (mFensterPlayer == null || mDragging) {
            return 0;
        }
        int position = mFensterPlayer.getCurrentPosition();
        int duration = mFensterPlayer.getDuration();
        if (mProgress != null) {
            if (duration > 0) {
                // use long to avoid overflow
                long pos = 1000L * position / duration;
                mProgress.setProgress((int) pos);
            }
            int percent = mFensterPlayer.getBufferPercentage();
            mProgress.setSecondaryProgress(percent * 10);
        }

        if (mEndTime != null) {
            mEndTime.setText(stringForTime(duration));
        }
        if (mCurrentTime != null) {
            mCurrentTime.setText(stringForTime(position));
        }
        final int playedSeconds = position / 1000;
        if (lastPlayedSeconds != playedSeconds) {
            lastPlayedSeconds = playedSeconds;
        }
        return position;
    }

    @Override
    public boolean onTrackballEvent(final MotionEvent ev) {
        show(DEFAULT_TIMEOUT);
        return false;
    }

    @Override
    public boolean dispatchKeyEvent(final KeyEvent event) {
        int keyCode = event.getKeyCode();
        final boolean uniqueDown = event.getRepeatCount() == 0
                && event.getAction() == KeyEvent.ACTION_DOWN;
        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
                || keyCode == KeyEvent.KEYCODE_SPACE) {
            if (uniqueDown) {
                doPauseResume();
                show(DEFAULT_TIMEOUT);
                if (mPauseButton != null) {
                    mPauseButton.requestFocus();
                }
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
            if (uniqueDown && !mFensterPlayer.isPlaying()) {
                mFensterPlayer.start();
                updatePausePlay();
                show(DEFAULT_TIMEOUT);
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
            if (uniqueDown && mFensterPlayer.isPlaying()) {
                mFensterPlayer.pause();
                updatePausePlay();
                show(DEFAULT_TIMEOUT);
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
                || keyCode == KeyEvent.KEYCODE_VOLUME_UP
                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
                || keyCode == KeyEvent.KEYCODE_CAMERA) {
            // don't show the controls for volume adjustment
            return super.dispatchKeyEvent(event);
        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
            if (uniqueDown) {
                hide();
            }
            return true;
        }

        show(DEFAULT_TIMEOUT);
        return super.dispatchKeyEvent(event);
    }

    private void updatePausePlay() {
        if (mPauseButton == null) {
            return;
        }

        if (mFensterPlayer.isPlaying()) {
            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
        } else {
            mPauseButton.setImageResource(android.R.drawable.ic_media_play);
        }
    }

    private void doPauseResume() {
        if (mFensterPlayer.isPlaying()) {
            mFensterPlayer.pause();
        } else {
            mFensterPlayer.start();
        }
        updatePausePlay();
    }

    @Override
    public void setEnabled(final boolean enabled) {
        if (mPauseButton != null) {
            mPauseButton.setEnabled(enabled);
        }
        if (mNextButton != null) {
            mNextButton.setEnabled(enabled);
        }
        if (mPrevButton != null) {
            mPrevButton.setEnabled(enabled);
        }
        if (mProgress != null) {
            mProgress.setEnabled(enabled);
        }
        if (mVolume != null) {
            mVolume.setEnabled(enabled);
        }
        if (mBrightness != null) {
            mBrightness.setEnabled(enabled);
        }
        super.setEnabled(enabled);
    }

    @Override
    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(MediaFensterPlayerController.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(MediaFensterPlayerController.class.getName());
    }

    @Override
    public void onTap() {
        Log.i(TAG, "Single Tap Up");
    }

    @Override
    public void onHorizontalScroll(MotionEvent event, float delta) {
        if (event.getPointerCount() == ONE_FINGER) {
            updateVideoProgressBar(delta);
        } else {
            if (delta > 0) {
                skipVideoForward();
            } else {
                skipVideoBackwards();
            }
        }
    }

    @Override
    public void onVerticalScroll(MotionEvent event, float delta) {
        if (event.getPointerCount() == ONE_FINGER) {
            updateVolumeProgressBar(-delta);
        } else {
            updateBrightnessProgressBar(-delta);
        }
    }

    @Override
    public void onSwipeRight() {
        skipVideoForward();
    }

    @Override
    public void onSwipeLeft() {
        skipVideoBackwards();
    }

    @Override
    public void onSwipeBottom() {

    }

    @Override
    public void onSwipeTop() {

    }

    @Override
    public void onVolumeStartedDragging() {
        mDragging = true;
    }

    @Override
    public void onVolumeFinishedDragging() {
        mDragging = false;
    }

    @Override
    public void onBrigthnessStartedDragging() {
        mDragging = true;
    }

    @Override
    public void onBrightnessFinishedDragging() {
        mDragging = false;
    }

    private void updateVolumeProgressBar(float delta) {
        mVolume.manuallyUpdate(extractVerticalDeltaScale(delta, mVolume));
    }

    private void updateBrightnessProgressBar(float delta) {
        mBrightness.manuallyUpdate((int) delta);
    }

    private void updateVideoProgressBar(float delta) {
        mSeekListener.onProgressChanged(mProgress, extractHorizontalDeltaScale(delta, mProgress), true);
    }

    private void skipVideoForward() {
        mSeekListener.onProgressChanged(mProgress, forwardSkippingUnit(), true);
    }

    private void skipVideoBackwards() {
        mSeekListener.onProgressChanged(mProgress, backwardSkippingUnit(), true);
    }

    private int extractHorizontalDeltaScale(float deltaX, SeekBar seekbar) {
        return extractDeltaScale(getWidth(), deltaX, seekbar);
    }

    private int extractVerticalDeltaScale(float deltaY, SeekBar seekbar) {
        return extractDeltaScale(getHeight(), deltaY, seekbar);
    }

    private int extractDeltaScale(int availableSpace, float deltaX, SeekBar seekbar) {
        int x = (int) deltaX;
        float scale;
        float progress = seekbar.getProgress();
        final int max = seekbar.getMax();

        if (x < 0) {
            scale = (float) (x) / (float) (max - availableSpace);
            progress = progress - (scale * progress);
        } else {
            scale = (float) (x) / (float) availableSpace;
            progress += scale * max;
        }

        return (int) progress;
    }

    private int forwardSkippingUnit() {
        return mProgress.getProgress() + SKIP_VIDEO_PROGRESS;
    }

    private int backwardSkippingUnit() {
        return mProgress.getProgress() - SKIP_VIDEO_PROGRESS;
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/controller/SimpleMediaFensterPlayerController.java
================================================
package com.malmstein.fenster.controller;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;

import com.malmstein.fenster.R;
import com.malmstein.fenster.play.FensterPlayer;
import com.malmstein.fenster.play.FensterVideoStateListener;
import com.malmstein.fenster.view.FensterTouchRoot;

import java.util.Formatter;
import java.util.Locale;

/**
 * Controller to manage syncing the ui models with the UI Controls and MediaPlayer.
 * <p/>
 * Note that the ui models have a narrow scope (i.e. chapter list, piece navigation),
 * their interaction is orchestrated by this controller.ø
 * <p/>
 * It's actually a view currently, as is the android MediaController.
 * (which is a bit odd and should be subject to change.)
 */
public final class SimpleMediaFensterPlayerController extends FrameLayout implements FensterPlayerController, FensterVideoStateListener, FensterTouchRoot.OnTouchReceiver {

    public static final String TAG = "PlayerController";
    public static final int DEFAULT_VIDEO_START = 0;
    private static final int DEFAULT_TIMEOUT = 5000;

    private static final int FADE_OUT = 1;
    private static final int SHOW_PROGRESS = 2;

    private FensterPlayerControllerVisibilityListener visibilityListener;
    private FensterPlayer mFensterPlayer;
    private boolean mShowing;
    private boolean mDragging;

    private boolean mLoading;
    private boolean mFirstTimeLoading = true;

    private StringBuilder mFormatBuilder;
    private Formatter mFormatter;
    private View bottomControlsRoot;
    private View controlsRoot;
    private ProgressBar mProgress;
    private TextView mEndTime;
    private TextView mCurrentTime;

    private ImageButton mPauseButton;
    private ImageButton mNextButton;
    private ImageButton mPrevButton;
    private ProgressBar loadingView;
    private int lastPlayedSeconds = -1;

    public SimpleMediaFensterPlayerController(final Context context) {
        this(context, null);
    }

    public SimpleMediaFensterPlayerController(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SimpleMediaFensterPlayerController(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onFinishInflate() {
        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_simple_media_controller, this);
        initControllerView();
    }

    @Override
    public void setMediaPlayer(final FensterPlayer fensterPlayer) {
        mFensterPlayer = fensterPlayer;
        updatePausePlay();
    }

    @Override
    public void setVisibilityListener(final FensterPlayerControllerVisibilityListener visibilityListener) {
        this.visibilityListener = visibilityListener;
    }

    private void initControllerView() {
        mPauseButton = (ImageButton) findViewById(R.id.fen__media_controller_pause);
        mPauseButton.requestFocus();
        mPauseButton.setOnClickListener(mPauseListener);

        mNextButton = (ImageButton) findViewById(R.id.fen__media_controller_next);
        mPrevButton = (ImageButton) findViewById(R.id.fen__media_controller_previous);

        mProgress = (SeekBar) findViewById(R.id.fen__media_controller_progress);
        SeekBar seeker = (SeekBar) mProgress;
        seeker.setOnSeekBarChangeListener(mSeekListener);
        mProgress.setMax(1000);

        mEndTime = (TextView) findViewById(R.id.fen__media_controller_time);
        mCurrentTime = (TextView) findViewById(R.id.fen__media_controller_time_current);
        mFormatBuilder = new StringBuilder();
        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());

        FensterTouchRoot touchRoot = (FensterTouchRoot) findViewById(R.id.media_controller_touch_root);
        touchRoot.setOnTouchReceiver(this);

        bottomControlsRoot = findViewById(R.id.fen__media_controller_bottom_area);
        bottomControlsRoot.setVisibility(View.INVISIBLE);

        controlsRoot = findViewById(R.id.media_controller_controls_root);
        controlsRoot.setVisibility(View.INVISIBLE);

        loadingView = (ProgressBar) findViewById(R.id.fen__media_controller_loading_view);
    }


    /**
     * Show the controller on screen. It will go away
     * automatically after the default time of inactivity.
     */
    @Override
    public void show() {
        show(DEFAULT_TIMEOUT);
    }

    /**
     * Show the controller on screen. It will go away
     * automatically after 'timeout' milliseconds of inactivity.
     *
     * @param timeInMilliSeconds The timeout in milliseconds. Use 0 to show
     *                           the controller until hide() is called.
     */
    @Override
    public void show(final int timeInMilliSeconds) {
        if (!mShowing) {
            setProgress();
            if (mPauseButton != null) {
                mPauseButton.requestFocus();
            }
            mShowing = true;
            setVisibility(View.VISIBLE);
        }

        updatePausePlay();

        // cause the progress bar to be updated even if mShowing
        // was already true.  This happens, for example, if we're
        // paused with the progress bar showing the user hits play.
        mHandler.sendEmptyMessage(SHOW_PROGRESS);

        Message msg = mHandler.obtainMessage(FADE_OUT);
        if (timeInMilliSeconds != 0) {
            mHandler.removeMessages(FADE_OUT);
            mHandler.sendMessageDelayed(msg, timeInMilliSeconds);
        }

        if (visibilityListener != null) {
            visibilityListener.onControlsVisibilityChange(true);
        }

    }

    public boolean isShowing() {
        return mShowing;
    }

    public boolean isLoading() {
        return mLoading;
    }

    public boolean isFirstTimeLoading() {
        return mFirstTimeLoading;
    }

    /**
     * Remove the controller from the screen.
     */
    @Override
    public void hide() {

        if (mShowing) {
            try {
                mHandler.removeMessages(SHOW_PROGRESS);
                setVisibility(View.INVISIBLE);
            } catch (final IllegalArgumentException ex) {
                Log.w("MediaController", "already removed");
            }
            mShowing = false;
        }
        if (visibilityListener != null) {
            visibilityListener.onControlsVisibilityChange(false);
        }
    }

    private String stringForTime(final int timeMs) {
        int totalSeconds = timeMs / 1000;

        int seconds = totalSeconds % 60;
        int minutes = (totalSeconds / 60) % 60;
        int hours = totalSeconds / 3600;

        mFormatBuilder.setLength(0);
        if (hours > 0) {
            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
        } else {
            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
        }
    }

    private int setProgress() {
        if (mFensterPlayer == null || mDragging) {
            return 0;
        }
        int position = mFensterPlayer.getCurrentPosition();
        int duration = mFensterPlayer.getDuration();
        if (mProgress != null) {
            if (duration > 0) {
                // use long to avoid overflow
                long pos = 1000L * position / duration;
                mProgress.setProgress((int) pos);
            }
            int percent = mFensterPlayer.getBufferPercentage();
            mProgress.setSecondaryProgress(percent * 10);
        }

        if (mEndTime != null) {
            mEndTime.setText(stringForTime(duration));
        }
        if (mCurrentTime != null) {
            mCurrentTime.setText(stringForTime(position));
        }
        final int playedSeconds = position / 1000;
        if (lastPlayedSeconds != playedSeconds) {
            lastPlayedSeconds = playedSeconds;
        }
        return position;
    }

    @Override
    public boolean onTrackballEvent(final MotionEvent ev) {
        show(DEFAULT_TIMEOUT);
        return false;
    }

    @Override
    public boolean dispatchKeyEvent(final KeyEvent event) {
        int keyCode = event.getKeyCode();
        final boolean uniqueDown = event.getRepeatCount() == 0
                && event.getAction() == KeyEvent.ACTION_DOWN;
        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
                || keyCode == KeyEvent.KEYCODE_SPACE) {
            if (uniqueDown) {
                doPauseResume();
                show(DEFAULT_TIMEOUT);
                if (mPauseButton != null) {
                    mPauseButton.requestFocus();
                }
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
            if (uniqueDown && !mFensterPlayer.isPlaying()) {
                mFensterPlayer.start();
                updatePausePlay();
                show(DEFAULT_TIMEOUT);
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
            if (uniqueDown && mFensterPlayer.isPlaying()) {
                mFensterPlayer.pause();
                updatePausePlay();
                show(DEFAULT_TIMEOUT);
            }
            return true;
        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
                || keyCode == KeyEvent.KEYCODE_VOLUME_UP
                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
                || keyCode == KeyEvent.KEYCODE_CAMERA) {
            // don't show the controls for volume adjustment
            return super.dispatchKeyEvent(event);
        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
            if (uniqueDown) {
                hide();
            }
            return true;
        }

        show(DEFAULT_TIMEOUT);
        return super.dispatchKeyEvent(event);
    }

    private void updatePausePlay() {
        if (mPauseButton == null) {
            return;
        }

        if (mFensterPlayer.isPlaying()) {
            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
        } else {
            mPauseButton.setImageResource(android.R.drawable.ic_media_play);
        }
    }

    private void doPauseResume() {
        if (mFensterPlayer.isPlaying()) {
            mFensterPlayer.pause();
        } else {
            mFensterPlayer.start();
        }
        updatePausePlay();
    }

    @Override
    public void setEnabled(final boolean enabled) {

        if (mPauseButton != null) {
            mPauseButton.setEnabled(enabled);
        }

        if (mNextButton != null) {
            mNextButton.setEnabled(enabled);
        }
        if (mPrevButton != null) {
            mPrevButton.setEnabled(enabled);
        }
        if (mProgress != null) {
            mProgress.setEnabled(enabled);
        }
        super.setEnabled(enabled);
    }

    @Override
    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(SimpleMediaFensterPlayerController.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(SimpleMediaFensterPlayerController.class.getName());
    }

    @Override
    public void onFirstVideoFrameRendered() {
        controlsRoot.setVisibility(View.VISIBLE);
        bottomControlsRoot.setVisibility(View.VISIBLE);
        mFirstTimeLoading = false;
    }

    @Override
    public void onPlay() {
        hideLoadingView();
    }

    @Override
    public void onBuffer() {
        showLoadingView();
    }

    @Override
    public boolean onStopWithExternalError(int position) {
        return false;
    }

    private void hideLoadingView() {
        hide();
        loadingView.setVisibility(View.GONE);

        mLoading = false;
    }

    private void showLoadingView() {
        mLoading = true;
        loadingView.setVisibility(View.VISIBLE);
    }

    // There are two scenarios that can trigger the seekbar listener to trigger:
    //
    // The first is the user using the touchpad to adjust the posititon of the
    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
    // We're setting the field "mDragging" to true for the duration of the dragging
    // session to avoid jumps in the position in case of ongoing playback.
    //
    // The second scenario involves the user operating the scroll ball, in this
    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
    // we will simply apply the updated position without suspending regular updates.
    private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
        public void onStartTrackingTouch(final SeekBar bar) {
            show(3600000);

            mDragging = true;

            // By removing these pending progress messages we make sure
            // that a) we won't update the progress while the user adjusts
            // the seekbar and b) once the user is done dragging the thumb
            // we will post one of these messages to the queue again and
            // this ensures that there will be exactly one message queued up.
            mHandler.removeMessages(SHOW_PROGRESS);
        }

        public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {
            if (!fromuser) {
                // We're not interested in programmatically generated changes to
                // the progress bar's position.
                return;
            }

            long duration = mFensterPlayer.getDuration();
            long newposition = (duration * progress) / 1000L;
            mFensterPlayer.seekTo((int) newposition);
            if (mCurrentTime != null) {
                mCurrentTime.setText(stringForTime((int) newposition));
            }
        }

        public void onStopTrackingTouch(final SeekBar bar) {
            mDragging = false;
            setProgress();
            updatePausePlay();
            show(DEFAULT_TIMEOUT);

            // Ensure that progress is properly updated in the future,
            // the call to show() does not guarantee this because it is a
            // no-op if we are already showing.
            mHandler.sendEmptyMessage(SHOW_PROGRESS);
        }
    };

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(final Message msg) {
            int pos;
            switch (msg.what) {
                case FADE_OUT:
                    if (mFensterPlayer.isPlaying()) {
                        hide();
                    } else {
                        // re-schedule to check again
                        Message fadeMessage = obtainMessage(FADE_OUT);
                        removeMessages(FADE_OUT);
                        sendMessageDelayed(fadeMessage, DEFAULT_TIMEOUT);
                    }
                    break;
                case SHOW_PROGRESS:
                    pos = setProgress();
                    if (!mDragging && mShowing && mFensterPlayer.isPlaying()) {
                        final Message message = obtainMessage(SHOW_PROGRESS);
                        sendMessageDelayed(message, 1000 - (pos % 1000));
                    }
                    break;
            }
        }
    };

    private final OnClickListener mPauseListener = new OnClickListener() {
        public void onClick(final View v) {
            doPauseResume();
            show(DEFAULT_TIMEOUT);
        }
    };

    /**
     * Called by ViewTouchRoot on user touches,
     * so we can avoid hiding the ui while the user is interacting.
     */
    @Override
    public void onControllerUiTouched() {
        if (mShowing) {
            Log.d(TAG, "controller ui touch received!");
            show();
        }
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterEventsListener.java
================================================
package com.malmstein.fenster.gestures;

import android.view.MotionEvent;

public interface FensterEventsListener {

    void onTap();

    void onHorizontalScroll(MotionEvent event, float delta);

    void onVerticalScroll(MotionEvent event, float delta);

    void onSwipeRight();

    void onSwipeLeft();

    void onSwipeBottom();

    void onSwipeTop();

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterGestureControllerView.java
================================================
package com.malmstein.fenster.gestures;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class FensterGestureControllerView extends View{

    private GestureDetector gestureDetector;
    private FensterEventsListener listener;

    public FensterGestureControllerView(Context context) {
        super(context);
    }

    public FensterGestureControllerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FensterGestureControllerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        setClickable(true);
        setFocusable(true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mayNotifyGestureDetector(event);
        return true;
    }

    private void mayNotifyGestureDetector(MotionEvent event){
        gestureDetector.onTouchEvent(event);
    }

    public void setFensterEventsListener(FensterEventsListener listener){
        gestureDetector = new GestureDetector(getContext(), new FensterGestureListener(listener, ViewConfiguration.get(getContext())));
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterGestureListener.java
================================================
package com.malmstein.fenster.gestures;

import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

public class FensterGestureListener implements GestureDetector.OnGestureListener {

    private static final int SWIPE_THRESHOLD = 100;
    private final int minFlingVelocity;

    public static final String TAG = "FensterGestureListener";
    private final FensterEventsListener listener;

    public FensterGestureListener(FensterEventsListener listener, ViewConfiguration viewConfiguration) {
        this.listener = listener;
        minFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        listener.onTap();
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        // Touch has been long enough to indicate a long press.
        // Does not indicate motion is complete yet (no up event necessarily)
        Log.i(TAG, "Long Press");
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.i(TAG, "Scroll");

        float deltaY = e2.getY() - e1.getY();
        float deltaX = e2.getX() - e1.getX();

        if (Math.abs(deltaX) > Math.abs(deltaY)) {
            if (Math.abs(deltaX) > SWIPE_THRESHOLD) {
                listener.onHorizontalScroll(e2, deltaX);
                if (deltaX > 0) {
                    Log.i(TAG, "Slide right");
                } else {
                    Log.i(TAG, "Slide left");
                }
            }
        } else {
            if (Math.abs(deltaY) > SWIPE_THRESHOLD) {
                listener.onVerticalScroll(e2, deltaY);
                if (deltaY > 0) {
                    Log.i(TAG, "Slide down");
                } else {
                    Log.i(TAG, "Slide up");
                }
            }
        }
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // Fling event occurred.  Notification of this one happens after an "up" event.
        Log.i(TAG, "Fling");
        boolean result = false;
        try {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();
            if (Math.abs(diffX) > Math.abs(diffY)) {
                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > minFlingVelocity) {
                    if (diffX > 0) {
                        listener.onSwipeRight();
                    } else {
                        listener.onSwipeLeft();
                    }
                }
                result = true;
            } else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > minFlingVelocity) {
                if (diffY > 0) {
                    listener.onSwipeBottom();
                } else {
                    listener.onSwipeTop();
                }
            }
            result = true;

        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return result;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        Log.i(TAG, "Show Press");
    }

    @Override
    public boolean onDown(MotionEvent e) {
        Log.i(TAG, "Down");
        return false;
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/helper/BrightnessHelper.java
================================================
package com.malmstein.fenster.helper;

import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;

public class BrightnessHelper {

    public static void setBrightness(Context context, int brightness){
        ContentResolver cResolver = context.getContentResolver();
        Settings.System.putInt(cResolver, Settings.System.SCREEN_BRIGHTNESS, brightness);
    }

    public static int getBrightness(Context context) {
        ContentResolver cResolver = context.getContentResolver();
        try {
            return Settings.System.getInt(cResolver, Settings.System.SCREEN_BRIGHTNESS);
        } catch (Settings.SettingNotFoundException e) {
            return 0;
        }
    }
}


================================================
FILE: library/src/main/java/com/malmstein/fenster/play/FensterPlayer.java
================================================
package com.malmstein.fenster.play;

public interface FensterPlayer {
    void start();

    void pause();

    int getDuration();

    /**
     * @return current playback position in milliseconds.
     */
    int getCurrentPosition();

    void seekTo(int pos);

    boolean isPlaying();

    int getBufferPercentage();

    boolean canPause();

    boolean canSeekBackward();

    boolean canSeekForward();

    /**
     * Get the audio session id for the player used by this VideoView. This can be used to
     * apply audio effects to the audio track of a video.
     *
     * @return The audio session, or 0 if there was an error.
     */
    int getAudioSessionId();

}

================================================
FILE: library/src/main/java/com/malmstein/fenster/play/FensterVideoFragment.java
================================================
package com.malmstein.fenster.play;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.malmstein.fenster.R;
import com.malmstein.fenster.controller.FensterPlayerControllerVisibilityListener;
import com.malmstein.fenster.controller.FensterPlayerController;
import com.malmstein.fenster.controller.SimpleMediaFensterPlayerController;
import com.malmstein.fenster.view.FensterLoadingView;
import com.malmstein.fenster.view.FensterVideoView;

public class FensterVideoFragment extends Fragment implements FensterVideoStateListener {

    private View contentView;
    private FensterVideoView textureView;
    private FensterPlayerController fensterPlayerController;
    private FensterLoadingView fensterLoadingView;

    public FensterVideoFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        contentView = inflater.inflate(R.layout.fen__fragment_fenster_video, container);
        textureView = (FensterVideoView) contentView.findViewById(R.id.fen__play_video_texture);
        fensterPlayerController = (FensterPlayerController) contentView.findViewById(R.id.fen__play_video_controller);
        fensterLoadingView = (FensterLoadingView) contentView.findViewById(R.id.fen__play_video_loading);
        return contentView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initVideo();
    }

    private void initVideo() {
        textureView.setMediaController(fensterPlayerController);
        textureView.setOnPlayStateListener(this);
    }

    public void playExampleVideo() {
        textureView.setVideo("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
                SimpleMediaFensterPlayerController.DEFAULT_VIDEO_START);
        textureView.start();
    }

    public void setVisibilityListener(FensterPlayerControllerVisibilityListener visibilityListener) {
        fensterPlayerController.setVisibilityListener(visibilityListener);
    }

    public void showFensterController() {
        fensterLoadingView.hide();
        fensterPlayerController.show();
    }

    private void showLoadingView(){
        fensterLoadingView.show();
        fensterPlayerController.hide();
    }

    @Override
    public void onFirstVideoFrameRendered() {
        fensterPlayerController.show();
    }

    @Override
    public void onPlay() {
        showFensterController();
    }

    @Override
    public void onBuffer() {
        showLoadingView();
    }

    @Override
    public boolean onStopWithExternalError(int position) {
        return false;
    }
}


================================================
FILE: library/src/main/java/com/malmstein/fenster/play/FensterVideoStateListener.java
================================================
package com.malmstein.fenster.play;

public interface FensterVideoStateListener {

    void onFirstVideoFrameRendered();

    void onPlay();

    void onBuffer();

    boolean onStopWithExternalError(int position);

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/seekbar/BrightnessSeekBar.java
================================================
package com.malmstein.fenster.seekbar;

import android.content.Context;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;

import com.malmstein.fenster.helper.BrightnessHelper;

public class BrightnessSeekBar extends SeekBar {

    public static final int MAX_BRIGHTNESS = 255;
    public static final int MIN_BRIGHTNESS = 0;
    public final OnSeekBarChangeListener brightnessSeekListener = new OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int brightness, boolean fromUser) {
            setBrightness(brightness);
            setProgress(brightness);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            brightnessListener.onBrigthnessStartedDragging();
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            brightnessListener.onBrightnessFinishedDragging();
        }
    };
    private Listener brightnessListener;

    public BrightnessSeekBar(Context context) {
        super(context);
    }

    public BrightnessSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BrightnessSeekBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(BrightnessSeekBar.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(BrightnessSeekBar.class.getName());
    }

    public void initialise(Listener brightnessListener) {
        this.setMax(MAX_BRIGHTNESS);
        this.setOnSeekBarChangeListener(brightnessSeekListener);
        this.brightnessListener = brightnessListener;
        manuallyUpdate(BrightnessHelper.getBrightness(getContext()));
    }

    public void setBrightness(int brightness) {
        if (brightness < MIN_BRIGHTNESS) {
            brightness = MIN_BRIGHTNESS;
        } else if (brightness > MAX_BRIGHTNESS) {
            brightness = MAX_BRIGHTNESS;
        }

        BrightnessHelper.setBrightness(getContext(), brightness);
    }

    public void manuallyUpdate(int update) {
        brightnessSeekListener.onProgressChanged(this, update, true);
    }

    public interface Listener {
        void onBrigthnessStartedDragging();

        void onBrightnessFinishedDragging();
    }
}


================================================
FILE: library/src/main/java/com/malmstein/fenster/seekbar/VolumeSeekBar.java
================================================
package com.malmstein.fenster.seekbar;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;

public class VolumeSeekBar extends SeekBar {

    public final OnSeekBarChangeListener volumeSeekListener = new OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int vol, boolean fromUser) {
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            volumeListener.onVolumeStartedDragging();
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            volumeListener.onVolumeFinishedDragging();
        }
    };

    private AudioManager audioManager;
    private Listener volumeListener;
    private BroadcastReceiver volumeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateVolumeProgress();
        }
    };

    public VolumeSeekBar(Context context) {
        super(context);
    }

    public VolumeSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VolumeSeekBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(VolumeSeekBar.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(VolumeSeekBar.class.getName());
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        registerVolumeReceiver();
    }

    @Override
    protected void onDetachedFromWindow() {
        unregisterVolumeReceiver();
        super.onDetachedFromWindow();
    }

    public void initialise(final Listener volumeListener) {
        this.audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
        this.volumeListener = volumeListener;

        this.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
        this.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
        this.setOnSeekBarChangeListener(volumeSeekListener);
    }

    private void updateVolumeProgress() {
        this.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
    }

    private void registerVolumeReceiver() {
        getContext().registerReceiver(volumeReceiver, new IntentFilter("android.media.VOLUME_CHANGED_ACTION"));
    }

    private void unregisterVolumeReceiver() {
        getContext().unregisterReceiver(volumeReceiver);
    }

    public void manuallyUpdate(int update) {
        volumeSeekListener.onProgressChanged(this, update, true);
    }

    public interface Listener {
        void onVolumeStartedDragging();

        void onVolumeFinishedDragging();
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/view/FensterLoadingView.java
================================================
package com.malmstein.fenster.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;

import com.malmstein.fenster.R;

public class FensterLoadingView extends FrameLayout {

    public FensterLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FensterLoadingView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        LayoutInflater.from(getContext()).inflate(R.layout.fen__view_loading, this);
    }

    public void show() {
        setVisibility(VISIBLE);
    }

    public void hide() {
        setVisibility(GONE);
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/view/FensterTouchRoot.java
================================================
package com.malmstein.fenster.view;

import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;

/**
 * A custom layout we put as a layout root to get notified about any screen touches.
 */
public final class FensterTouchRoot extends FrameLayout {

    public static final int MIN_INTERCEPTION_TIME = 1000;
    private long lastInterception;

    private OnTouchReceiver touchReceiver;

    public FensterTouchRoot(final Context context) {
        super(context);
    }

    public FensterTouchRoot(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public FensterTouchRoot(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean dispatchTouchEvent(final MotionEvent ev) {
        if (touchReceiver != null) {
            final long timeStamp = SystemClock.elapsedRealtime();
            // we throttle the touch event dispatch to avoid event spam
            if (timeStamp - lastInterception > MIN_INTERCEPTION_TIME) {
                lastInterception = timeStamp;
                touchReceiver.onControllerUiTouched();
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    public void setOnTouchReceiver(final OnTouchReceiver receiver) {
        this.touchReceiver = receiver;
    }

    public interface OnTouchReceiver {
        void onControllerUiTouched();
    }
}

================================================
FILE: library/src/main/java/com/malmstein/fenster/view/FensterVideoView.java
================================================
package com.malmstein.fenster.view;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.content.res.TypedArray;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.MediaController;

import com.malmstein.fenster.R;
import com.malmstein.fenster.controller.FensterPlayerController;
import com.malmstein.fenster.play.FensterPlayer;
import com.malmstein.fenster.play.FensterVideoStateListener;

import java.io.IOException;
import java.util.Map;

/**
 * Displays a video file.  The VideoView class
 * can load images from various sources (such as resources or content
 * providers), takes care of computing its measurement from the video so that
 * it can be used in any layout manager, and provides various display options
 * such as scaling and tinting.<p>
 * <p/>
 * <em>Note: VideoView does not retain its full state when going into the
 * background.</em>  In particular, it does not restore the current play state,
 * play position, selected tracks added via
 * {@link android.app.Activity#onSaveInstanceState} and
 * {@link android.app.Activity#onRestoreInstanceState}.<p>
 * Also note that the audio session id (from {@link #getAudioSessionId}) may
 * change from its previously returned value when the VideoView is restored.
 */

public class FensterVideoView extends TextureView implements MediaController.MediaPlayerControl, FensterPlayer {

    public static final String TAG = "TextureVideoView";
    public static final int VIDEO_BEGINNING = 0;

    public enum ScaleType {
        SCALE_TO_FIT, CROP
    }

    // all possible internal states
    private static final int STATE_ERROR = -1;
    private static final int STATE_IDLE = 0;
    private static final int STATE_PREPARING = 1;
    private static final int STATE_PREPARED = 2;
    private static final int STATE_PLAYING = 3;
    private static final int STATE_PAUSED = 4;
    private static final int STATE_PLAYBACK_COMPLETED = 5;
    private static final int MILLIS_IN_SEC = 1000;

    // collaborators / delegates / composites .. discuss
    private final VideoSizeCalculator videoSizeCalculator;
    // mCurrentState is a VideoView object's current state.
    // mTargetState is the state that a method caller intends to reach.
    // For instance, regardless the VideoView object's current state,
    // calling pause() intends to bring the object to a target state
    // of STATE_PAUSED.
    private int mCurrentState = STATE_IDLE;
    private int mTargetState = STATE_IDLE;

    private ScaleType mScaleType;

    private Uri mUri;

    private AssetFileDescriptor mAssetFileDescriptor;
    private Map<String, String> mHeaders;
    private SurfaceTexture mSurfaceTexture;
    private MediaPlayer mMediaPlayer = null;
    private FensterPlayerController fensterPlayerController;
    private OnCompletionListener mOnCompletionListener;
    private MediaPlayer.OnPreparedListener mOnPreparedListener;
    private OnErrorListener mOnErrorListener;
    private OnInfoListener mOnInfoListener;
    private int mAudioSession;
    private int mSeekWhenPrepared;  // recording the seek position while preparing
    private int mCurrentBufferPercentage;
    private boolean mCanPause;
    private boolean mCanSeekBack;
    private boolean mCanSeekForward;
    private FensterVideoStateListener onPlayStateListener;

    private AlertDialog errorDialog;

    public FensterVideoView(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FensterVideoView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
        applyCustomAttributes(context, attrs);
        videoSizeCalculator = new VideoSizeCalculator();
        initVideoView();
    }

    private void applyCustomAttributes(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FensterVideoView);
        if (typedArray == null) {
            return;
        }
        int[] attrsValues = {R.attr.scaleType};
        TypedArray scaleTypedArray = context.obtainStyledAttributes(attrs, attrsValues);
        if (scaleTypedArray != null) {
            try {
                int scaleTypeId = typedArray.getInt(0, 0);
                mScaleType = ScaleType.values()[scaleTypeId];
            } finally {
                typedArray.recycle();
            }
        } else {
            mScaleType = ScaleType.SCALE_TO_FIT;
        }
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        VideoSizeCalculator.Dimens dimens = videoSizeCalculator.measure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(dimens.getWidth(), dimens.getHeight());
    }

    @Override
    public void onInitializeAccessibilityEvent(final AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(FensterVideoView.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(FensterVideoView.class.getName());
    }

    public int resolveAdjustedSize(final int desiredSize, final int measureSpec) {
        return getDefaultSize(desiredSize, measureSpec);
    }

    private void initVideoView() {
        videoSizeCalculator.setVideoSize(0, 0);

        setSurfaceTextureListener(mSTListener);

        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();
        mCurrentState = STATE_IDLE;
        mTargetState = STATE_IDLE;
        setOnInfoListener(onInfoToPlayStateListener);
    }

    private void disableFileDescriptor() {
        mAssetFileDescriptor = null;
    }

    public void setVideo(final String path) {
        disableFileDescriptor();
        setVideo(Uri.parse(path), VIDEO_BEGINNING);
    }

    public void setVideo(final String url, final int seekInSeconds) {
        disableFileDescriptor();
        setVideo(Uri.parse(url), seekInSeconds);
    }

    public void setVideo(final Uri uri, final int seekInSeconds) {
        disableFileDescriptor();
        setVideoURI(uri, null, seekInSeconds);
    }

    public void setVideo(final AssetFileDescriptor assetFileDescriptor) {
        mAssetFileDescriptor = assetFileDescriptor;
        setVideoURI(null, null, VIDEO_BEGINNING);
    }

    public void setVideo(final AssetFileDescriptor assetFileDescriptor, final int seekInSeconds) {
        mAssetFileDescriptor = assetFileDescriptor;
        setVideoURI(null, null, seekInSeconds);
    }

    /**
     * Set the scale type of the video, needs to be set after setVideo() has been called
     *
     * @param scaleType
     */
    private void setScaleType(ScaleType scaleType) {
        switch (scaleType) {
            case SCALE_TO_FIT:
                mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
                break;
            case CROP:
                mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
                break;
        }
    }

    private void setVideoURI(final Uri uri, final Map<String, String> headers, final int seekInSeconds) {
        Log.d(TAG, "start playing: " + uri);
        mUri = uri;
        mHeaders = headers;
        mSeekWhenPrepared = seekInSeconds * 1000;
        openVideo();
        requestLayout();
        invalidate();
    }

    public void stopPlayback() {
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            setKeepScreenOn(false);
            mCurrentState = STATE_IDLE;
            mTargetState = STATE_IDLE;
        }
    }

    private void openVideo() {
        if (notReadyForPlaybackJustYetWillTryAgainLater()) {
            return;
        }
        tellTheMusicPlaybackServiceToPause();

        // we shouldn't clear the target state, because somebody might have called start() previously
        release(false);
        try {
            mMediaPlayer = new MediaPlayer();

            if (mAudioSession != 0) {
                mMediaPlayer.setAudioSessionId(mAudioSession);
            } else {
                mAudioSession = mMediaPlayer.getAudioSessionId();
            }
            mMediaPlayer.setOnPreparedListener(mPreparedListener);
            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
            mMediaPlayer.setOnCompletionListener(mCompletionListener);
            mMediaPlayer.setOnErrorListener(mErrorListener);
            mMediaPlayer.setOnInfoListener(mInfoListener);
            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
            mCurrentBufferPercentage = 0;

            setDataSource();
            setScaleType(mScaleType);

            mMediaPlayer.setSurface(new Surface(mSurfaceTexture));
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setScreenOnWhilePlaying(true);
            mMediaPlayer.prepareAsync();

            // we don't set the target state here either, but preserve the target state that was there before.
            mCurrentState = STATE_PREPARING;
            attachMediaController();
        } catch (final IOException | IllegalArgumentException ex) {
            notifyUnableToOpenContent(ex);
        }
    }

    private void setDataSource() throws IOException {
        if (mAssetFileDescriptor != null) {
            mMediaPlayer.setDataSource(
                    mAssetFileDescriptor.getFileDescriptor(),
                    mAssetFileDescriptor.getStartOffset(),
                    mAssetFileDescriptor.getLength()
            );
        } else {
            mMediaPlayer.setDataSource(getContext(), mUri, mHeaders);
        }
    }

    private boolean notReadyForPlaybackJustYetWillTryAgainLater() {
        return mSurfaceTexture == null;
    }

    private void tellTheMusicPlaybackServiceToPause() {
        // these constants need to be published somewhere in the framework.
        Intent i = new Intent("com.android.music.musicservicecommand");
        i.putExtra("command", "pause");
        getContext().sendBroadcast(i);
    }

    private void notifyUnableToOpenContent(final Exception ex) {
        Log.w("Unable to open content:" + mUri, ex);
        mCurrentState = STATE_ERROR;
        mTargetState = STATE_ERROR;
        mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    }

    public void setMediaController(final FensterPlayerController controller) {
        hideMediaController();
        fensterPlayerController = controller;
        attachMediaController();
    }

    private void attachMediaController() {
        if (mMediaPlayer != null && fensterPlayerController != null) {
            fensterPlayerController.setMediaPlayer(this);
//            View anchorView = this.getParent() instanceof View ? (View) this.getParent() : this;
//            fensterPlayerController.setAnchorView(anchorView);
            fensterPlayerController.setEnabled(isInPlaybackState());
        }
    }

    private MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) {
            videoSizeCalculator.setVideoSize(mp.getVideoWidth(), mp.getVideoHeight());
            if (videoSizeCalculator.hasASizeYet()) {
                requestLayout();
            }
        }
    };

    private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(final MediaPlayer mp) {
            mCurrentState = STATE_PREPARED;

            mCanPause = true;
            mCanSeekBack = true;
            mCanSeekForward = true;

            if (mOnPreparedListener != null) {
                mOnPreparedListener.onPrepared(mMediaPlayer);
            }
            if (fensterPlayerController != null) {
                fensterPlayerController.setEnabled(true);
            }
            videoSizeCalculator.setVideoSize(mp.getVideoWidth(), mp.getVideoHeight());

            int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
            if (seekToPosition != 0) {
                seekTo(seekToPosition);
            }

            if (mTargetState == STATE_PLAYING) {
                start();
                showMediaController();
            } else if (pausedAt(seekToPosition)) {
                showStickyMediaController();
            }
        }
    };

    private boolean pausedAt(final int seekToPosition) {
        return !isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0);
    }

    private void showStickyMediaController() {
        if (fensterPlayerController != null) {
            fensterPlayerController.show(0);
        }
    }

    private OnCompletionListener mCompletionListener = new OnCompletionListener() {

        @Override
        public void onCompletion(final MediaPlayer mp) {
            setKeepScreenOn(false);
            mCurrentState = STATE_PLAYBACK_COMPLETED;
            mTargetState = STATE_PLAYBACK_COMPLETED;
            hideMediaController();
            if (mOnCompletionListener != null) {
                mOnCompletionListener.onCompletion(mMediaPlayer);
            }
        }
    };

    private OnInfoListener mInfoListener = new OnInfoListener() {
        @Override
        public boolean onInfo(final MediaPlayer mp, final int arg1, final int arg2) {
            if (mOnInfoListener != null) {
                mOnInfoListener.onInfo(mp, arg1, arg2);
            }
            return true;
        }
    };

    private OnErrorListener mErrorListener = new OnErrorListener() {
        @Override
        public boolean onError(final MediaPlayer mp, final int frameworkError, final int implError) {
            Log.d(TAG, "Error: " + frameworkError + "," + implError);
            if (mCurrentState == STATE_ERROR) {
                return true;
            }
            mCurrentState = STATE_ERROR;
            mTargetState = STATE_ERROR;
            hideMediaController();

            if (allowPlayStateToHandle(frameworkError)) {
                return true;
            }

            if (allowErrorListenerToHandle(frameworkError, implError)) {
                return true;
            }

            handleError(frameworkError);
            return true;
        }
    };

    private void hideMediaController() {
        if (fensterPlayerController != null) {
            fensterPlayerController.hide();
        }
    }

    private void showMediaController() {
        if (fensterPlayerController != null) {
            fensterPlayerController.show();
        }
    }

    private boolean allowPlayStateToHandle(final int frameworkError) {
        if (frameworkError == MediaPlayer.MEDIA_ERROR_UNKNOWN || frameworkError == MediaPlayer.MEDIA_ERROR_IO) {
            Log.e(TAG, "TextureVideoView error. File or network related operation errors.");
            if (hasPlayStateListener()) {
                return onPlayStateListener.onStopWithExternalError(mMediaPlayer.getCurrentPosition() / MILLIS_IN_SEC);
            }
        }
        return false;
    }

    private boolean allowErrorListenerToHandle(final int frameworkError, final int implError) {
        if (mOnErrorListener != null) {
            return mOnErrorListener.onError(mMediaPlayer, frameworkError, implError);
        }

        return false;
    }

    private void handleError(final int frameworkError) {
        if (getWindowToken() != null) {
            if (errorDialog != null && errorDialog.isShowing()) {
                Log.d(TAG, "Dismissing last error dialog for a new one");
                errorDialog.dismiss();
            }
            errorDialog = createErrorDialog(this.getContext(), mOnCompletionListener, mMediaPlayer, getErrorMessage(frameworkError));
            errorDialog.show();
        }
    }

    private static AlertDialog createErrorDialog(final Context context, final OnCompletionListener completionListener, final MediaPlayer mediaPlayer, final int errorMessage) {
        return new AlertDialog.Builder(context)
                .setMessage(errorMessage)
                .setPositiveButton(
                        android.R.string.ok,
                        new DialogInterface.OnClickListener() {
                            public void onClick(final DialogInterface dialog, final int whichButton) {
                                    /* If we get here, there is no onError listener, so
                                     * at least inform them that the video is over.
                                     */
                                if (completionListener != null) {
                                    completionListener.onCompletion(mediaPlayer);
                                }
                            }
                        }
                )
                .setCancelable(false)
                .create();
    }

    private static int getErrorMessage(final int frameworkError) {
        int messageId = R.string.fen__play_error_message;

        if (frameworkError == MediaPlayer.MEDIA_ERROR_IO) {
            Log.e(TAG, "TextureVideoView error. File or network related operation errors.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_MALFORMED) {
            Log.e(TAG, "TextureVideoView error. Bitstream is not conforming to the related coding standard or file spec.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
            Log.e(TAG, "TextureVideoView error. Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
            Log.e(TAG, "TextureVideoView error. Some operation takes too long to complete, usually more than 3-5 seconds.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_UNKNOWN) {
            Log.e(TAG, "TextureVideoView error. Unspecified media player error.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {
            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.");
        } else if (frameworkError == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
            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.");
            messageId = R.string.fen__play_progressive_error_message;
        }
        return messageId;
    }

    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
        @Override
        public void onBufferingUpdate(final MediaPlayer mp, final int percent) {
            mCurrentBufferPercentage = percent;
        }
    };

    /**
     * Register a callback to be invoked when the media file
     * is loaded and ready to go.
     *
     * @param l The callback that will be run
     */
    public void setOnPreparedListener(final MediaPlayer.OnPreparedListener l) {
        mOnPreparedListener = l;
    }

    /**
     * Register a callback to be invoked when the end of a media file
     * has been reached during playback.
     *
     * @param l The callback that will be run
     */
    public void setOnCompletionListener(final OnCompletionListener l) {
        mOnCompletionListener = l;
    }

    /**
     * Register a callback to be invoked when an error occurs
     * during playback or setup.  If no listener is specified,
     * or if the listener returned false, VideoView will inform
     * the user of any errors.
     *
     * @param l The callback that will be run
     */
    public void setOnErrorListener(final OnErrorListener l) {
        mOnErrorListener = l;
    }

    /**
     * Register a callback to be invoked when an informational event
     * occurs during playback or setup.
     *
     * @param l The callback that will be run
     */
    private void setOnInfoListener(final OnInfoListener l) {
        mOnInfoListener = l;
    }

    private SurfaceTextureListener mSTListener = new SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
            mSurfaceTexture = surface;
            openVideo();
        }

        @Override
        public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
            boolean isValidState = (mTargetState == STATE_PLAYING);
            boolean hasValidSize = videoSizeCalculator.currentSizeIs(width, height);
            if (mMediaPlayer != null && isValidState && hasValidSize) {
                if (mSeekWhenPrepared != 0) {
                    seekTo(mSeekWhenPrepared);
                }
                start();
            }
        }

        @Override
        public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {
            mSurfaceTexture = null;
            hideMediaController();
            release(true);
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
            mSurfaceTexture = surface;
        }
    };

    /*
     * release the media player in any state
     */
    private void release(final boolean clearTargetState) {
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
            mCurrentState = STATE_IDLE;
            if (clearTargetState) {
                mTargetState = STATE_IDLE;
            }
        }
    }

    @Override
    public boolean onTrackballEvent(final MotionEvent ev) {
        if (isInPlaybackState() && fensterPlayerController != null) {
            fensterPlayerController.show();
        }
        return false;
    }

    @Override
    public boolean onKeyDown(final int keyCode, final KeyEvent event) {
        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
                keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
                keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
                keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
                keyCode != KeyEvent.KEYCODE_MENU &&
                keyCode != KeyEvent.KEYCODE_CALL &&
                keyCode != KeyEvent.KEYCODE_ENDCALL;
        if (isInPlaybackState() && isKeyCodeSupported && fensterPlayerController != null) {
            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
                if (mMediaPlayer.isPlaying()) {
                    pause();
                    showMediaController();
                } else {
                    start();
                    hideMediaController();
                }
                return true;
            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
                if (!mMediaPlayer.isPlaying()) {
                    start();
                    hideMediaController();
                }
                return true;
            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
                if (mMediaPlayer.isPlaying()) {
                    pause();
                    showMediaController();
                }
                return true;
            } else {
                fensterPlayerController.show();
            }
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void start() {
        if (isInPlaybackState()) {
            mMediaPlayer.start();
            setKeepScreenOn(true);
            mCurrentState = STATE_PLAYING;
        }
        mTargetState = STATE_PLAYING;
    }

    @Override
    public void pause() {
        if (isInPlaybackState()) {
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
                mCurrentState = STATE_PAUSED;
                setKeepScreenOn(false);
            }
        }
        mTargetState = STATE_PAUSED;
    }

    public void suspend() {
        release(false);
    }

    public void resume() {
        openVideo();
    }

    @Override
    public int getDuration() {
        if (isInPlaybackState()) {
            return mMediaPlayer.getDuration();
        }

        return -1;
    }

    /**
     * @return current position in milliseconds
     */
    @Override
    public int getCurrentPosition() {
        if (isInPlaybackState()) {
            return mMediaPlayer.getCurrentPosition();
        }
        return 0;
    }

    public int getCurrentPositionInSeconds() {
        return getCurrentPosition() / MILLIS_IN_SEC;
    }

    @Override
    public void seekTo(final int millis) {
        if (isInPlaybackState()) {
            mMediaPlayer.seekTo(millis);
            mSeekWhenPrepared = 0;
        } else {
            mSeekWhenPrepared = millis;
        }
    }

    public void seekToSeconds(final int seconds) {
        seekTo(seconds * MILLIS_IN_SEC);
        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
            @Override
            public void onSeekComplete(final MediaPlayer mp) {
                Log.i(TAG, "seek completed");
            }
        });
    }

    @Override
    public boolean isPlaying() {
        return isInPlaybackState() && mMediaPlayer.isPlaying();
    }

    @Override
    public int getBufferPercentage() {
        if (mMediaPlayer != null) {
            return mCurrentBufferPercentage;
        }
        return 0;
    }

    private boolean isInPlaybackState() {
        return (mMediaPlayer != null &&
                mCurrentState != STATE_ERROR &&
                mCurrentState != STATE_IDLE &&
                mCurrentState != STATE_PREPARING);
    }

    @Override
    public boolean canPause() {
        return mCanPause;
    }

    @Override
    public boolean canSeekBackward() {
        return mCanSeekBack;
    }

    @Override
    public boolean canSeekForward() {
        return mCanSeekForward;
    }

    @Override
    public int getAudioSessionId() {
        if (mAudioSession == 0) {
            MediaPlayer foo = new MediaPlayer();
            mAudioSession = foo.getAudioSessionId();
            foo.release();
        }
        return mAudioSession;
    }

    private final OnInfoListener onInfoToPlayStateListener = new OnInfoListener() {

        @Override
        public boolean onInfo(final MediaPlayer mp, final int what, final int extra) {
            if (noPlayStateListener()) {
                return false;
            }

            if (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START == what) {
                onPlayStateListener.onFirstVideoFrameRendered();
                onPlayStateListener.onPlay();
            }
            if (MediaPlayer.MEDIA_INFO_BUFFERING_START == what) {
                onPlayStateListener.onBuffer();
            }
            if (MediaPlayer.MEDIA_INFO_BUFFERING_END == what) {
                onPlayStateListener.onPlay();
            }

            return false;
        }
    };

    private boolean noPlayStateListener() {
        return !hasPlayStateListener();
    }

    private boolean hasPlayStateListener() {
        return onPlayStateListener != null;
    }

    public void setOnPlayStateListener(final FensterVideoStateListener onPlayStateListener) {
        this.onPlayStateListener = onPlayStateListener;
    }

}


================================================
FILE: library/src/main/java/com/malmstein/fenster/view/VideoSizeCalculator.java
================================================
package com.malmstein.fenster.view;

import android.view.SurfaceHolder;
import android.view.View;

public class VideoSizeCalculator {

    private Dimens dimens;

    private int mVideoWidth;
    private int mVideoHeight;

    public VideoSizeCalculator() {
        dimens = new Dimens();
    }

    public void setVideoSize(int mVideoWidth, int mVideoHeight) {
        this.mVideoWidth = mVideoWidth;
        this.mVideoHeight = mVideoHeight;
    }

    public boolean hasASizeYet() {
        return mVideoWidth > 0 && mVideoHeight > 0;
    }

    protected Dimens measure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
        int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
        if (hasASizeYet()) {

            int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
            int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
            int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);

            if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
                // the size is fixed
                width = widthSpecSize;
                height = heightSpecSize;

                // for compatibility, we adjust size based on aspect ratio
                if (mVideoWidth * height < width * mVideoHeight) {
                    width = height * mVideoWidth / mVideoHeight;
                } else if (mVideoWidth * height > width * mVideoHeight) {
                    height = width * mVideoHeight / mVideoWidth;
                }
            } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
                // only the width is fixed, adjust the height to match aspect ratio if possible
                width = widthSpecSize;
                height = width * mVideoHeight / mVideoWidth;
                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
                    // couldn't match aspect ratio within the constraints
                    height = heightSpecSize;
                }
            } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
                // only the height is fixed, adjust the width to match aspect ratio if possible
                height = heightSpecSize;
                width = height * mVideoWidth / mVideoHeight;
                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
                    // couldn't match aspect ratio within the constraints
                    width = widthSpecSize;
                }
            } else {
                // neither the width nor the height are fixed, try to use actual video size
                width = mVideoWidth;
                height = mVideoHeight;
                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
                    // too tall, decrease both width and height
                    height = heightSpecSize;
                    width = height * mVideoWidth / mVideoHeight;
                }
                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
                    // too wide, decrease both width and height
                    width = widthSpecSize;
                    height = width * mVideoHeight / mVideoWidth;
                }
            }
        }
        dimens.width = width;
        dimens.height = height;
        return dimens;
    }

    public boolean currentSizeIs(int w, int h) {
        return mVideoWidth == w && mVideoHeight == h;
    }

    public void updateHolder(SurfaceHolder holder) {
        holder.setFixedSize(mVideoWidth, mVideoHeight);
    }

    static class Dimens {
        int width;
        int height;

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }
    }

}


================================================
FILE: library/src/main/res/layout/fen__fragment_fenster_gesture.xml
================================================
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="@dimen/fen__padding_medium">

  <com.malmstein.fenster.gestures.FensterGestureControllerView
    android:id="@+id/fen__play_gesture_controller"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

  <SeekBar
    android:id="@+id/fen__play_gesture_horizontal_seekbar"
    android:layout_width="match_parent"
    android:layout_centerInParent="true"
    android:max="1000"
    android:layout_height="@dimen/fen__media_controller_seekbar_height"/>

  <SeekBar
    android:id="@+id/fen__play_gesture_vertical_seekbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/fen__media_controller_seekbar_height"
    android:max="1000"
    android:rotation="90" />

</RelativeLayout>


================================================
FILE: library/src/main/res/layout/fen__fragment_fenster_video.xml
================================================
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.malmstein.fenster.view.FensterVideoView
    android:id="@+id/fen__play_video_texture"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    android:fitsSystemWindows="true" />

  <com.malmstein.fenster.controller.MediaFensterPlayerController
    android:id="@+id/fen__play_video_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true" />

  <com.malmstein.fenster.view.FensterLoadingView
    android:id="@+id/fen__play_video_loading"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</RelativeLayout>


================================================
FILE: library/src/main/res/layout/fen__view_loading.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/fen__media_controller_loading_view"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center"/>


================================================
FILE: library/src/main/res/layout/fen__view_media_controller.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/media_controller_root"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:layout_gravity="bottom"
  android:fitsSystemWindows="true"
  android:gravity="bottom">

  <com.malmstein.fenster.gestures.FensterGestureControllerView
    android:id="@+id/media_controller_gestures_area"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@+id/media_controller_bottom_root" />

  <LinearLayout
    android:id="@+id/media_controller_bottom_root"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    android:background="@color/fen__default_bg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/fen__padding_small"
    android:paddingBottom="@dimen/fen__padding_small"
    android:paddingLeft="@dimen/fen__padding_small"
    android:paddingRight="@dimen/fen__padding_extra_large"
    android:orientation="vertical">

    <LinearLayout
      android:id="@+id/fen__media_controller_bottom_area"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:gravity="center">

      <TextView
        android:id="@+id/fen__media_controller_time_current"
        style="@style/MediaText"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

      <SeekBar
        android:id="@+id/fen__media_controller_progress"
        android:layout_width="0dip"
        android:layout_height="@dimen/fen__media_controller_seekbar_height"
        android:layout_weight="8" />

      <TextView
        android:id="@+id/fen__media_controller_time"
        style="@style/MediaText"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    </LinearLayout>

    <LinearLayout
      android:id="@+id/fen__media_controller_controls"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      android:orientation="horizontal">

      <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/fen__ic_action_bulb"/>

      <com.malmstein.fenster.seekbar.BrightnessSeekBar
        android:id="@+id/fen__media_controller_brightness"
        android:layout_width="0dip"
        android:layout_height="@dimen/fen__media_controller_seekbar_height"
        android:layout_weight="3" />

      <ImageButton
        android:id="@+id/fen__media_controller_previous"
        style="@style/MediaButton.Previous" />

      <ImageButton
        android:id="@+id/fen__media_controller_pause"
        style="@style/MediaButton.Play" />

      <ImageButton
        android:id="@+id/fen__media_controller_next"
        style="@style/MediaButton.Next"
        android:contentDescription="Next" />

      <com.malmstein.fenster.seekbar.VolumeSeekBar
        android:id="@+id/fen__media_controller_volume"
        android:layout_width="0dip"
        android:layout_height="@dimen/fen__media_controller_seekbar_height"
        android:layout_weight="3" />

      <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/fen__ic_action_music_2" />

    </LinearLayout>

  </LinearLayout>

</RelativeLayout>



================================================
FILE: library/src/main/res/layout/fen__view_simple_media_controller.xml
================================================
<?xml version="1.0" encoding="utf-8"?>

<com.malmstein.fenster.view.FensterTouchRoot xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/media_controller_touch_root"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/fen__default_bg">

  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
      android:id="@+id/fen__media_controller_bottom_area"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:orientation="horizontal"
      android:layout_marginBottom="@dimen/fen__media_controller_bottom_margin"
      android:gravity="top">

      <TextView
        android:id="@+id/fen__media_controller_time_current"
        style="@style/MediaText"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

      <SeekBar
        android:id="@+id/fen__media_controller_progress"
        android:layout_width="0dip"
        android:layout_height="@dimen/fen__media_controller_seekbar_height"
        android:layout_weight="8" />

      <TextView
        android:id="@+id/fen__media_controller_time"
        style="@style/MediaText"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    </LinearLayout>


    <LinearLayout
      android:id="@+id/media_controller_controls_root"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_above="@+id/fen__media_controller_bottom_area"
      android:gravity="center"
      android:orientation="horizontal">

      <ImageButton
        android:id="@+id/fen__media_controller_previous"
        style="@style/MediaButton.Previous"
        android:contentDescription="Previous Piece" />

      <ImageButton
        android:id="@+id/fen__media_controller_pause"
        style="@style/MediaButton.Play" />

      <ImageButton
        android:id="@+id/fen__media_controller_next"
        style="@style/MediaButton.Next"
        android:contentDescription="Next Piece" />

    </LinearLayout>

    <ProgressBar
      android:id="@+id/fen__media_controller_loading_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignTop="@id/media_controller_controls_root"
      android:layout_alignBottom="@id/media_controller_controls_root"
      android:layout_centerInParent="true" />

  </RelativeLayout>

</com.malmstein.fenster.view.FensterTouchRoot>


================================================
FILE: library/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

  <attr name="scaleType" format="enum">
    <enum name="scaleToFit" value="0" />
    <enum name="crop" value="1" />
  </attr>

  <declare-styleable name="FensterVideoView">
    <attr name="scaleType" />
  </declare-styleable>

</resources>


================================================
FILE: library/src/main/res/values/fen__colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

  <color name="fen__default_bg">#aa000000</color>

</resources>


================================================
FILE: library/src/main/res/values/fen__dimens.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

  <!-- PADDINGS -->
  <dimen name="fen__padding_none">0dp</dimen>
  <dimen name="fen__padding_small">2dp</dimen>
  <dimen name="fen__padding_medium">4dp</dimen>
  <dimen name="fen__padding_extra_large">48dp</dimen>

  <!-- MEDIA CONTROLLER -->
  <dimen name="fen__media_controller_text_size">14sp</dimen>
  <dimen name="fen__media_controller_seekbar_width">32dip</dimen>
  <dimen name="fen__media_controller_seekbar_height">32dip</dimen>
  <dimen name="fen__media_controller_top_margin">80dip</dimen>
  <dimen name="fen__media_controller_bottom_margin">12dip</dimen>

  <dimen name="fen__media_controller_button_height">52dip</dimen>
  <dimen name="fen__media_controller_button_width">71dip</dimen>

</resources>


================================================
FILE: library/src/main/res/values/fen__strings.xml
================================================
<resources>
  <string name="fen__app_name">Fenster</string>
  <string name="fen__play_error_message">Impossible to play the video.</string>
  <string name="fen__play_progressive_error_message">Impossible to play the progressive video.</string>
</resources>


================================================
FILE: library/src/main/res/values/fen__styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

  <style name="MediaButton">
    <item name="android:background">@null</item>
    <item name="android:layout_width">@dimen/fen__media_controller_button_width</item>
    <item name="android:layout_height">@dimen/fen__media_controller_button_height</item>
  </style>

  <style name="MediaButton.Previous">
    <item name="android:src">@android:drawable/ic_media_rew</item>
  </style>

  <style name="MediaButton.Play">
    <item name="android:src">@android:drawable/ic_media_play</item>
  </style>

  <style name="MediaButton.Next">
    <item name="android:src">@android:drawable/ic_media_ff</item>
  </style>

  <style name="MediaText">
    <item name="android:textSize">@dimen/fen__media_controller_text_size</item>
    <item name="android:textStyle">bold</item>
    <item name="android:paddingTop">@dimen/fen__padding_medium</item>
    <item name="android:textColor">@android:color/white</item>
    <item name="android:gravity">center_horizontal</item>
  </style>

</resources>


================================================
FILE: library/src/main/res/values/public.xml
================================================
<resources>
  <public name='fen__app_name' type='string' />
</resources>


================================================
FILE: settings.gradle
================================================
include ':app', ':library'


================================================
FILE: team-props/bintray.gradle
================================================
if (project.plugins.hasPlugin('com.android.library')) {
    apply from: 'https://gist.githubusercontent.com/hal9002/691bbf3340dde3a4591d/raw' // Android plugin
} else {
    apply from: 'https://gist.githubusercontent.com/hal9002/810b1d3e841fd6160c5d/raw' // Java plugin
}

apply plugin: 'com.jfrog.bintray'

def localReleaseDest = "${buildDir}/release"

def getString(String propertyName) {
    project.hasProperty(propertyName) ? project.getProperty(propertyName) : ''
}

def getBoolean(String propertyName) {
    project.hasProperty(propertyName) ? Boolean.valueOf(project.getProperty(propertyName)) : true
}

bintray {
    user = getString('BINTRAY_USER')
    key = getString('BINTRAY_KEY')
    publish = getBoolean('UPLOAD_AUTO_PUBLISH')

    filesSpec {
        from localReleaseDest
        into "."
        exclude '**/maven-metadata.*'
    }

    pkg {
        repo = 'maven'
        userOrg = 'novoda'
        name = getString('UPLOAD_NAME')
        desc = getString('UPLOAD_DESCRIPTION')
        websiteUrl = getString('UPLOAD_WEBSITE')
        issueTrackerUrl = getString('UPLOAD_ISSUE_TRACKER')
        vcsUrl = getString('UPLOAD_REPOSITORY')

        licenses = ['Apache-2.0']
        version {
            name = getString('UPLOAD_VERSION')
        }
    }
}

task publishReleaseToBintray(dependsOn: ['generateRelease', 'bintrayUpload'])
bintrayUpload.mustRunAfter generateRelease
publishReleaseToBintray.mustRunAfter bintrayUpload

gradle.taskGraph.useFilter { task ->
    shouldBeExecuted(task)
}

def shouldBeExecuted(def task) {
    !isBintrayRelated(task) || shouldPublishToBintray()
}

def isBintrayRelated(def task) {
    task.name.toLowerCase().contains('bintray')
}

def shouldPublishToBintray() {
    getBoolean('MANUAL_PUBLISH')
}

================================================
FILE: team-props/deploy.gradle
================================================
apply plugin: 'maven'

def groupId = project.PUBLISH_GROUP_ID
def artifactId = project.PUBLISH_ARTIFACT_ID
def version = project.PUBLISH_VERSION

def localReleaseDest = "${buildDir}/release"

task androidJavadocs(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
    classifier = 'javadoc'
    from androidJavadocs.destinationDir
}

task androidSourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.srcDirs
}

uploadArchives {
    repositories.mavenDeployer {
        pom.groupId = groupId
        pom.artifactId = artifactId
        pom.version = version
        // Add other pom properties here if you want (developer details / licenses)
        repository(url: "file://${localReleaseDest}")
    }
}

task generateRelease << {
    println "Release ${version} can be found at ${localReleaseDest}/"
}
generateRelease.dependsOn build
generateRelease.dependsOn uploadArchives

artifacts {
    archives androidSourcesJar
    archives androidJavadocsJar
}
Download .txt
gitextract_0wxaa4k2/

├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── malmstein/
│       │               └── fenster/
│       │                   └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── malmstein/
│           │           └── fenster/
│           │               └── demo/
│           │                   ├── GestureMediaPlayerActivity.java
│           │                   ├── MediaPlayerDemoActivity.java
│           │                   ├── ScaleMediaPlayerActivity.java
│           │                   └── SimpleMediaPlayerActivity.java
│           └── res/
│               ├── layout/
│               │   ├── activity_demo.xml
│               │   ├── activity_gesture_media_player.xml
│               │   ├── activity_scale_media_player.xml
│               │   └── activity_simple_media_player.xml
│               ├── menu/
│               │   └── demo_menu.xml
│               ├── values/
│               │   ├── attrs.xml
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── publish.gradle
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── malmstein/
│       │               └── fenster/
│       │                   └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── malmstein/
│           │           └── fenster/
│           │               ├── controller/
│           │               │   ├── FensterPlayerController.java
│           │               │   ├── FensterPlayerControllerVisibilityListener.java
│           │               │   ├── MediaFensterPlayerController.java
│           │               │   └── SimpleMediaFensterPlayerController.java
│           │               ├── gestures/
│           │               │   ├── FensterEventsListener.java
│           │               │   ├── FensterGestureControllerView.java
│           │               │   └── FensterGestureListener.java
│           │               ├── helper/
│           │               │   └── BrightnessHelper.java
│           │               ├── play/
│           │               │   ├── FensterPlayer.java
│           │               │   ├── FensterVideoFragment.java
│           │               │   └── FensterVideoStateListener.java
│           │               ├── seekbar/
│           │               │   ├── BrightnessSeekBar.java
│           │               │   └── VolumeSeekBar.java
│           │               └── view/
│           │                   ├── FensterLoadingView.java
│           │                   ├── FensterTouchRoot.java
│           │                   ├── FensterVideoView.java
│           │                   └── VideoSizeCalculator.java
│           └── res/
│               ├── layout/
│               │   ├── fen__fragment_fenster_gesture.xml
│               │   ├── fen__fragment_fenster_video.xml
│               │   ├── fen__view_loading.xml
│               │   ├── fen__view_media_controller.xml
│               │   └── fen__view_simple_media_controller.xml
│               └── values/
│                   ├── attrs.xml
│                   ├── fen__colors.xml
│                   ├── fen__dimens.xml
│                   ├── fen__strings.xml
│                   ├── fen__styles.xml
│                   └── public.xml
├── settings.gradle
└── team-props/
    ├── bintray.gradle
    └── deploy.gradle
Download .txt
SYMBOL INDEX (310 symbols across 23 files)

FILE: app/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: app/src/main/java/com/malmstein/fenster/demo/GestureMediaPlayerActivity.java
  class GestureMediaPlayerActivity (line 10) | public class GestureMediaPlayerActivity extends Activity implements Fens...
    method onCreate (line 12) | @Override
    method onPostCreate (line 18) | @Override
    method initVideo (line 24) | private void initVideo(){
    method findVideoFragment (line 29) | private FensterVideoFragment findVideoFragment(){
    method onControlsVisibilityChange (line 33) | @Override
    method setSystemUiVisibility (line 38) | private void setSystemUiVisibility(final boolean visible) {

FILE: app/src/main/java/com/malmstein/fenster/demo/MediaPlayerDemoActivity.java
  class MediaPlayerDemoActivity (line 10) | public class MediaPlayerDemoActivity extends Activity implements View.On...
    method onCreate (line 12) | @Override
    method bindViews (line 20) | private void bindViews() {
    method onClick (line 27) | @Override
    method onCreateOptionsMenu (line 47) | @Override
    method onOptionsItemSelected (line 53) | @Override

FILE: app/src/main/java/com/malmstein/fenster/demo/ScaleMediaPlayerActivity.java
  class ScaleMediaPlayerActivity (line 9) | public class ScaleMediaPlayerActivity extends Activity {
    method onCreate (line 14) | @Override
    method onPostCreate (line 24) | @Override
    method bindViews (line 31) | private void bindViews() {
    method initVideo (line 36) | private void initVideo() {

FILE: app/src/main/java/com/malmstein/fenster/demo/SimpleMediaPlayerActivity.java
  class SimpleMediaPlayerActivity (line 12) | public class SimpleMediaPlayerActivity extends Activity implements Fenst...
    method onCreate (line 18) | @Override
    method onPostCreate (line 28) | @Override
    method bindViews (line 42) | private void bindViews() {
    method initVideo (line 47) | private void initVideo() {
    method setSystemUiVisibility (line 53) | private void setSystemUiVisibility(final boolean visible) {
    method onControlsVisibilityChange (line 76) | @Override

FILE: library/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: library/src/main/java/com/malmstein/fenster/controller/FensterPlayerController.java
  type FensterPlayerController (line 5) | public interface FensterPlayerController {
    method setMediaPlayer (line 7) | void setMediaPlayer(FensterPlayer fensterPlayer);
    method setEnabled (line 9) | void setEnabled(boolean value);
    method show (line 11) | void show(int timeInMilliSeconds);
    method show (line 13) | void show();
    method hide (line 15) | void hide();
    method setVisibilityListener (line 17) | void setVisibilityListener(FensterPlayerControllerVisibilityListener v...

FILE: library/src/main/java/com/malmstein/fenster/controller/FensterPlayerControllerVisibilityListener.java
  type FensterPlayerControllerVisibilityListener (line 9) | public interface FensterPlayerControllerVisibilityListener {
    method onControlsVisibilityChange (line 11) | void onControlsVisibilityChange(boolean value);

FILE: library/src/main/java/com/malmstein/fenster/controller/MediaFensterPlayerController.java
  class MediaFensterPlayerController (line 38) | public final class MediaFensterPlayerController extends RelativeLayout i...
    method onClick (line 55) | public void onClick(final View v) {
    method handleMessage (line 67) | @Override
    method onStartTrackingTouch (line 114) | public void onStartTrackingTouch(final SeekBar bar) {
    method onProgressChanged (line 127) | public void onProgressChanged(final SeekBar bar, final int progress, f...
    method onStopTrackingTouch (line 142) | public void onStopTrackingTouch(final SeekBar bar) {
    method MediaFensterPlayerController (line 159) | public MediaFensterPlayerController(final Context context) {
    method MediaFensterPlayerController (line 163) | public MediaFensterPlayerController(final Context context, final Attri...
    method MediaFensterPlayerController (line 167) | public MediaFensterPlayerController(final Context context, final Attri...
    method onFinishInflate (line 171) | @Override
    method setMediaPlayer (line 177) | @Override
    method setVisibilityListener (line 183) | public void setVisibilityListener(final FensterPlayerControllerVisibil...
    method initControllerView (line 187) | private void initControllerView() {
    method show (line 220) | @Override
    method show (line 232) | @Override
    method showBottomArea (line 263) | private void showBottomArea() {
    method isShowing (line 267) | public boolean isShowing() {
    method isFirstTimeLoading (line 271) | public boolean isFirstTimeLoading() {
    method hide (line 278) | @Override
    method stringForTime (line 297) | private String stringForTime(final int timeMs) {
    method setProgress (line 312) | private int setProgress() {
    method onTrackballEvent (line 341) | @Override
    method dispatchKeyEvent (line 347) | @Override
    method updatePausePlay (line 395) | private void updatePausePlay() {
    method doPauseResume (line 407) | private void doPauseResume() {
    method setEnabled (line 416) | @Override
    method onInitializeAccessibilityEvent (line 439) | @Override
    method onInitializeAccessibilityNodeInfo (line 445) | @Override
    method onTap (line 451) | @Override
    method onHorizontalScroll (line 456) | @Override
    method onVerticalScroll (line 469) | @Override
    method onSwipeRight (line 478) | @Override
    method onSwipeLeft (line 483) | @Override
    method onSwipeBottom (line 488) | @Override
    method onSwipeTop (line 493) | @Override
    method onVolumeStartedDragging (line 498) | @Override
    method onVolumeFinishedDragging (line 503) | @Override
    method onBrigthnessStartedDragging (line 508) | @Override
    method onBrightnessFinishedDragging (line 513) | @Override
    method updateVolumeProgressBar (line 518) | private void updateVolumeProgressBar(float delta) {
    method updateBrightnessProgressBar (line 522) | private void updateBrightnessProgressBar(float delta) {
    method updateVideoProgressBar (line 526) | private void updateVideoProgressBar(float delta) {
    method skipVideoForward (line 530) | private void skipVideoForward() {
    method skipVideoBackwards (line 534) | private void skipVideoBackwards() {
    method extractHorizontalDeltaScale (line 538) | private int extractHorizontalDeltaScale(float deltaX, SeekBar seekbar) {
    method extractVerticalDeltaScale (line 542) | private int extractVerticalDeltaScale(float deltaY, SeekBar seekbar) {
    method extractDeltaScale (line 546) | private int extractDeltaScale(int availableSpace, float deltaX, SeekBa...
    method forwardSkippingUnit (line 563) | private int forwardSkippingUnit() {
    method backwardSkippingUnit (line 567) | private int backwardSkippingUnit() {

FILE: library/src/main/java/com/malmstein/fenster/controller/SimpleMediaFensterPlayerController.java
  class SimpleMediaFensterPlayerController (line 37) | public final class SimpleMediaFensterPlayerController extends FrameLayou...
    method SimpleMediaFensterPlayerController (line 68) | public SimpleMediaFensterPlayerController(final Context context) {
    method SimpleMediaFensterPlayerController (line 72) | public SimpleMediaFensterPlayerController(final Context context, final...
    method SimpleMediaFensterPlayerController (line 76) | public SimpleMediaFensterPlayerController(final Context context, final...
    method onFinishInflate (line 80) | @Override
    method setMediaPlayer (line 86) | @Override
    method setVisibilityListener (line 92) | @Override
    method initControllerView (line 97) | private void initControllerView() {
    method show (line 132) | @Override
    method show (line 144) | @Override
    method isShowing (line 174) | public boolean isShowing() {
    method isLoading (line 178) | public boolean isLoading() {
    method isFirstTimeLoading (line 182) | public boolean isFirstTimeLoading() {
    method hide (line 189) | @Override
    method stringForTime (line 206) | private String stringForTime(final int timeMs) {
    method setProgress (line 221) | private int setProgress() {
    method onTrackballEvent (line 250) | @Override
    method dispatchKeyEvent (line 256) | @Override
    method updatePausePlay (line 304) | private void updatePausePlay() {
    method doPauseResume (line 316) | private void doPauseResume() {
    method setEnabled (line 325) | @Override
    method onInitializeAccessibilityEvent (line 344) | @Override
    method onInitializeAccessibilityNodeInfo (line 350) | @Override
    method onFirstVideoFrameRendered (line 356) | @Override
    method onPlay (line 363) | @Override
    method onBuffer (line 368) | @Override
    method onStopWithExternalError (line 373) | @Override
    method hideLoadingView (line 378) | private void hideLoadingView() {
    method showLoadingView (line 385) | private void showLoadingView() {
    method onStartTrackingTouch (line 402) | public void onStartTrackingTouch(final SeekBar bar) {
    method onProgressChanged (line 415) | public void onProgressChanged(final SeekBar bar, final int progress, f...
    method onStopTrackingTouch (line 430) | public void onStopTrackingTouch(final SeekBar bar) {
    method handleMessage (line 444) | @Override
    method onClick (line 470) | public void onClick(final View v) {
    method onControllerUiTouched (line 480) | @Override

FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterEventsListener.java
  type FensterEventsListener (line 5) | public interface FensterEventsListener {
    method onTap (line 7) | void onTap();
    method onHorizontalScroll (line 9) | void onHorizontalScroll(MotionEvent event, float delta);
    method onVerticalScroll (line 11) | void onVerticalScroll(MotionEvent event, float delta);
    method onSwipeRight (line 13) | void onSwipeRight();
    method onSwipeLeft (line 15) | void onSwipeLeft();
    method onSwipeBottom (line 17) | void onSwipeBottom();
    method onSwipeTop (line 19) | void onSwipeTop();

FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterGestureControllerView.java
  class FensterGestureControllerView (line 10) | public class FensterGestureControllerView extends View{
    method FensterGestureControllerView (line 15) | public FensterGestureControllerView(Context context) {
    method FensterGestureControllerView (line 19) | public FensterGestureControllerView(Context context, AttributeSet attr...
    method FensterGestureControllerView (line 23) | public FensterGestureControllerView(Context context, AttributeSet attr...
    method onFinishInflate (line 27) | @Override
    method onTouchEvent (line 34) | @Override
    method mayNotifyGestureDetector (line 40) | private void mayNotifyGestureDetector(MotionEvent event){
    method setFensterEventsListener (line 44) | public void setFensterEventsListener(FensterEventsListener listener){

FILE: library/src/main/java/com/malmstein/fenster/gestures/FensterGestureListener.java
  class FensterGestureListener (line 8) | public class FensterGestureListener implements GestureDetector.OnGesture...
    method FensterGestureListener (line 16) | public FensterGestureListener(FensterEventsListener listener, ViewConf...
    method onSingleTapUp (line 21) | @Override
    method onLongPress (line 27) | @Override
    method onScroll (line 34) | @Override
    method onFling (line 63) | @Override
    method onShowPress (line 95) | @Override
    method onDown (line 100) | @Override

FILE: library/src/main/java/com/malmstein/fenster/helper/BrightnessHelper.java
  class BrightnessHelper (line 7) | public class BrightnessHelper {
    method setBrightness (line 9) | public static void setBrightness(Context context, int brightness){
    method getBrightness (line 14) | public static int getBrightness(Context context) {

FILE: library/src/main/java/com/malmstein/fenster/play/FensterPlayer.java
  type FensterPlayer (line 3) | public interface FensterPlayer {
    method start (line 4) | void start();
    method pause (line 6) | void pause();
    method getDuration (line 8) | int getDuration();
    method getCurrentPosition (line 13) | int getCurrentPosition();
    method seekTo (line 15) | void seekTo(int pos);
    method isPlaying (line 17) | boolean isPlaying();
    method getBufferPercentage (line 19) | int getBufferPercentage();
    method canPause (line 21) | boolean canPause();
    method canSeekBackward (line 23) | boolean canSeekBackward();
    method canSeekForward (line 25) | boolean canSeekForward();
    method getAudioSessionId (line 33) | int getAudioSessionId();

FILE: library/src/main/java/com/malmstein/fenster/play/FensterVideoFragment.java
  class FensterVideoFragment (line 16) | public class FensterVideoFragment extends Fragment implements FensterVid...
    method FensterVideoFragment (line 23) | public FensterVideoFragment() {
    method onCreateView (line 26) | @Override
    method onViewCreated (line 35) | @Override
    method initVideo (line 41) | private void initVideo() {
    method playExampleVideo (line 46) | public void playExampleVideo() {
    method setVisibilityListener (line 52) | public void setVisibilityListener(FensterPlayerControllerVisibilityLis...
    method showFensterController (line 56) | public void showFensterController() {
    method showLoadingView (line 61) | private void showLoadingView(){
    method onFirstVideoFrameRendered (line 66) | @Override
    method onPlay (line 71) | @Override
    method onBuffer (line 76) | @Override
    method onStopWithExternalError (line 81) | @Override

FILE: library/src/main/java/com/malmstein/fenster/play/FensterVideoStateListener.java
  type FensterVideoStateListener (line 3) | public interface FensterVideoStateListener {
    method onFirstVideoFrameRendered (line 5) | void onFirstVideoFrameRendered();
    method onPlay (line 7) | void onPlay();
    method onBuffer (line 9) | void onBuffer();
    method onStopWithExternalError (line 11) | boolean onStopWithExternalError(int position);

FILE: library/src/main/java/com/malmstein/fenster/seekbar/BrightnessSeekBar.java
  class BrightnessSeekBar (line 11) | public class BrightnessSeekBar extends SeekBar {
    method onProgressChanged (line 16) | @Override
    method onStartTrackingTouch (line 22) | @Override
    method onStopTrackingTouch (line 27) | @Override
    method BrightnessSeekBar (line 34) | public BrightnessSeekBar(Context context) {
    method BrightnessSeekBar (line 38) | public BrightnessSeekBar(Context context, AttributeSet attrs) {
    method BrightnessSeekBar (line 42) | public BrightnessSeekBar(Context context, AttributeSet attrs, int defS...
    method onInitializeAccessibilityEvent (line 46) | @Override
    method onInitializeAccessibilityNodeInfo (line 52) | @Override
    method initialise (line 58) | public void initialise(Listener brightnessListener) {
    method setBrightness (line 65) | public void setBrightness(int brightness) {
    method manuallyUpdate (line 75) | public void manuallyUpdate(int update) {
    type Listener (line 79) | public interface Listener {
      method onBrigthnessStartedDragging (line 80) | void onBrigthnessStartedDragging();
      method onBrightnessFinishedDragging (line 82) | void onBrightnessFinishedDragging();

FILE: library/src/main/java/com/malmstein/fenster/seekbar/VolumeSeekBar.java
  class VolumeSeekBar (line 13) | public class VolumeSeekBar extends SeekBar {
    method onProgressChanged (line 16) | @Override
    method onStartTrackingTouch (line 21) | @Override
    method onStopTrackingTouch (line 26) | @Override
    method onReceive (line 35) | @Override
    method VolumeSeekBar (line 41) | public VolumeSeekBar(Context context) {
    method VolumeSeekBar (line 45) | public VolumeSeekBar(Context context, AttributeSet attrs) {
    method VolumeSeekBar (line 49) | public VolumeSeekBar(Context context, AttributeSet attrs, int defStyle) {
    method onInitializeAccessibilityEvent (line 53) | @Override
    method onInitializeAccessibilityNodeInfo (line 59) | @Override
    method onAttachedToWindow (line 65) | @Override
    method onDetachedFromWindow (line 71) | @Override
    method initialise (line 77) | public void initialise(final Listener volumeListener) {
    method updateVolumeProgress (line 86) | private void updateVolumeProgress() {
    method registerVolumeReceiver (line 90) | private void registerVolumeReceiver() {
    method unregisterVolumeReceiver (line 94) | private void unregisterVolumeReceiver() {
    method manuallyUpdate (line 98) | public void manuallyUpdate(int update) {
    type Listener (line 102) | public interface Listener {
      method onVolumeStartedDragging (line 103) | void onVolumeStartedDragging();
      method onVolumeFinishedDragging (line 105) | void onVolumeFinishedDragging();

FILE: library/src/main/java/com/malmstein/fenster/view/FensterLoadingView.java
  class FensterLoadingView (line 10) | public class FensterLoadingView extends FrameLayout {
    method FensterLoadingView (line 12) | public FensterLoadingView(Context context, AttributeSet attrs) {
    method FensterLoadingView (line 16) | public FensterLoadingView(Context context, AttributeSet attrs, int def...
    method onFinishInflate (line 20) | @Override
    method show (line 26) | public void show() {
    method hide (line 30) | public void hide() {

FILE: library/src/main/java/com/malmstein/fenster/view/FensterTouchRoot.java
  class FensterTouchRoot (line 12) | public final class FensterTouchRoot extends FrameLayout {
    method FensterTouchRoot (line 19) | public FensterTouchRoot(final Context context) {
    method FensterTouchRoot (line 23) | public FensterTouchRoot(final Context context, final AttributeSet attr...
    method FensterTouchRoot (line 27) | public FensterTouchRoot(final Context context, final AttributeSet attr...
    method dispatchTouchEvent (line 31) | @Override
    method setOnTouchReceiver (line 44) | public void setOnTouchReceiver(final OnTouchReceiver receiver) {
    type OnTouchReceiver (line 48) | public interface OnTouchReceiver {
      method onControllerUiTouched (line 49) | void onControllerUiTouched();

FILE: library/src/main/java/com/malmstein/fenster/view/FensterVideoView.java
  class FensterVideoView (line 50) | public class FensterVideoView extends TextureView implements MediaContro...
    type ScaleType (line 55) | public enum ScaleType {
    method FensterVideoView (line 102) | public FensterVideoView(final Context context, final AttributeSet attr...
    method FensterVideoView (line 106) | public FensterVideoView(final Context context, final AttributeSet attr...
    method applyCustomAttributes (line 113) | private void applyCustomAttributes(Context context, AttributeSet attrs) {
    method onMeasure (line 132) | @Override
    method onInitializeAccessibilityEvent (line 138) | @Override
    method onInitializeAccessibilityNodeInfo (line 144) | @Override
    method resolveAdjustedSize (line 150) | public int resolveAdjustedSize(final int desiredSize, final int measur...
    method initVideoView (line 154) | private void initVideoView() {
    method disableFileDescriptor (line 167) | private void disableFileDescriptor() {
    method setVideo (line 171) | public void setVideo(final String path) {
    method setVideo (line 176) | public void setVideo(final String url, final int seekInSeconds) {
    method setVideo (line 181) | public void setVideo(final Uri uri, final int seekInSeconds) {
    method setVideo (line 186) | public void setVideo(final AssetFileDescriptor assetFileDescriptor) {
    method setVideo (line 191) | public void setVideo(final AssetFileDescriptor assetFileDescriptor, fi...
    method setScaleType (line 201) | private void setScaleType(ScaleType scaleType) {
    method setVideoURI (line 212) | private void setVideoURI(final Uri uri, final Map<String, String> head...
    method stopPlayback (line 222) | public void stopPlayback() {
    method openVideo (line 233) | private void openVideo() {
    method setDataSource (line 273) | private void setDataSource() throws IOException {
    method notReadyForPlaybackJustYetWillTryAgainLater (line 285) | private boolean notReadyForPlaybackJustYetWillTryAgainLater() {
    method tellTheMusicPlaybackServiceToPause (line 289) | private void tellTheMusicPlaybackServiceToPause() {
    method notifyUnableToOpenContent (line 296) | private void notifyUnableToOpenContent(final Exception ex) {
    method setMediaController (line 303) | public void setMediaController(final FensterPlayerController controlle...
    method attachMediaController (line 309) | private void attachMediaController() {
    method onVideoSizeChanged (line 319) | @Override
    method onPrepared (line 329) | @Override
    method pausedAt (line 359) | private boolean pausedAt(final int seekToPosition) {
    method showStickyMediaController (line 363) | private void showStickyMediaController() {
    method onCompletion (line 371) | @Override
    method onInfo (line 384) | @Override
    method onError (line 394) | @Override
    method hideMediaController (line 417) | private void hideMediaController() {
    method showMediaController (line 423) | private void showMediaController() {
    method allowPlayStateToHandle (line 429) | private boolean allowPlayStateToHandle(final int frameworkError) {
    method allowErrorListenerToHandle (line 439) | private boolean allowErrorListenerToHandle(final int frameworkError, f...
    method handleError (line 447) | private void handleError(final int frameworkError) {
    method createErrorDialog (line 458) | private static AlertDialog createErrorDialog(final Context context, fi...
    method getErrorMessage (line 478) | private static int getErrorMessage(final int frameworkError) {
    method onBufferingUpdate (line 501) | @Override
    method setOnPreparedListener (line 513) | public void setOnPreparedListener(final MediaPlayer.OnPreparedListener...
    method setOnCompletionListener (line 523) | public void setOnCompletionListener(final OnCompletionListener l) {
    method setOnErrorListener (line 535) | public void setOnErrorListener(final OnErrorListener l) {
    method setOnInfoListener (line 545) | private void setOnInfoListener(final OnInfoListener l) {
    method onSurfaceTextureAvailable (line 550) | @Override
    method onSurfaceTextureSizeChanged (line 556) | @Override
    method onSurfaceTextureDestroyed (line 568) | @Override
    method onSurfaceTextureUpdated (line 576) | @Override
    method release (line 585) | private void release(final boolean clearTargetState) {
    method onTrackballEvent (line 597) | @Override
    method onKeyDown (line 605) | @Override
    method start (line 644) | @Override
    method pause (line 654) | @Override
    method suspend (line 666) | public void suspend() {
    method resume (line 670) | public void resume() {
    method getDuration (line 674) | @Override
    method getCurrentPosition (line 686) | @Override
    method getCurrentPositionInSeconds (line 694) | public int getCurrentPositionInSeconds() {
    method seekTo (line 698) | @Override
    method seekToSeconds (line 708) | public void seekToSeconds(final int seconds) {
    method isPlaying (line 718) | @Override
    method getBufferPercentage (line 723) | @Override
    method isInPlaybackState (line 731) | private boolean isInPlaybackState() {
    method canPause (line 738) | @Override
    method canSeekBackward (line 743) | @Override
    method canSeekForward (line 748) | @Override
    method getAudioSessionId (line 753) | @Override
    method onInfo (line 765) | @Override
    method noPlayStateListener (line 786) | private boolean noPlayStateListener() {
    method hasPlayStateListener (line 790) | private boolean hasPlayStateListener() {
    method setOnPlayStateListener (line 794) | public void setOnPlayStateListener(final FensterVideoStateListener onP...

FILE: library/src/main/java/com/malmstein/fenster/view/VideoSizeCalculator.java
  class VideoSizeCalculator (line 6) | public class VideoSizeCalculator {
    method VideoSizeCalculator (line 13) | public VideoSizeCalculator() {
    method setVideoSize (line 17) | public void setVideoSize(int mVideoWidth, int mVideoHeight) {
    method hasASizeYet (line 22) | public boolean hasASizeYet() {
    method measure (line 26) | protected Dimens measure(int widthMeasureSpec, int heightMeasureSpec) {
    method currentSizeIs (line 84) | public boolean currentSizeIs(int w, int h) {
    method updateHolder (line 88) | public void updateHolder(SurfaceHolder holder) {
    class Dimens (line 92) | static class Dimens {
      method getWidth (line 96) | public int getWidth() {
      method getHeight (line 100) | public int getHeight() {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (162K chars).
[
  {
    "path": ".gitignore",
    "chars": 521,
    "preview": "# 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/\n"
  },
  {
    "path": ".travis.yml",
    "chars": 212,
    "preview": "language: android\n\nandroid:\n  components:\n    - build-tools-23.0.1\n    - android-23\n    - extra-android-support\n    - ex"
  },
  {
    "path": "LICENSE.txt",
    "chars": 10748,
    "preview": "     Copyright 2014 David Gonzalez\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not us"
  },
  {
    "path": "README.md",
    "chars": 8631,
    "preview": "Fenster\n=============================\n\nA library to display videos in a `TextureView` using a custom `MediaPlayer` contr"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 757,
    "preview": "buildscript {\n    repositories {\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath 'co"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 667,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /A"
  },
  {
    "path": "app/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java",
    "chars": 352,
    "preview": "package com.malmstein.fenster;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1428,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  package=\"c"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/GestureMediaPlayerActivity.java",
    "chars": 2073,
    "preview": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.view.View;\n\ni"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/MediaPlayerDemoActivity.java",
    "chars": 2131,
    "preview": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundl"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/ScaleMediaPlayerActivity.java",
    "chars": 1302,
    "preview": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport com.malmstein.fenste"
  },
  {
    "path": "app/src/main/java/com/malmstein/fenster/demo/SimpleMediaPlayerActivity.java",
    "chars": 2986,
    "preview": "package com.malmstein.fenster.demo;\n\nimport android.app.Activity;\nimport android.content.res.AssetFileDescriptor;\nimport"
  },
  {
    "path": "app/src/main/res/layout/activity_demo.xml",
    "chars": 1782,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  androi"
  },
  {
    "path": "app/src/main/res/layout/activity_gesture_media_player.xml",
    "chars": 295,
    "preview": "<fragment xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  "
  },
  {
    "path": "app/src/main/res/layout/activity_scale_media_player.xml",
    "chars": 997,
    "preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:app=\"http://schemas.android.com/apk/res"
  },
  {
    "path": "app/src/main/res/layout/activity_simple_media_player.xml",
    "chars": 870,
    "preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/too"
  },
  {
    "path": "app/src/main/res/menu/demo_menu.xml",
    "chars": 346,
    "preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/tools\"\n  tool"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "chars": 225,
    "preview": "<resources>\n\n  <declare-styleable name=\"ButtonBarContainerTheme\">\n    <attr name=\"metaButtonBarStyle\" format=\"reference\""
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 124,
    "preview": "<resources>\n\n    <color name=\"black_overlay\">#66000000</color>\n    <color name=\"default_bg\">#aa000000</color>\n\n</resourc"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 135,
    "preview": "<resources>\n  <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n  <dimen name=\"activity_vertical_margin\">16dp</dimen"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 970,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <string name=\"app_name\">Fenster</string>\n  <string name=\"descripti"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 755,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\n  <style name=\"FensterTheme\" parent=\"android:Theme.Holo.Light.DarkA"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 81,
    "preview": "<resources>\n  <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "chars": 305,
    "preview": "buildscript {\n    repositories {\n        mavenCentral()\n        jcenter()\n    }\n    dependencies {\n        classpath 'co"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Fri Aug 28 15:24:47 BST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 853,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any "
  },
  {
    "path": "gradlew",
    "chars": 5080,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "library/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "chars": 1463,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.novoda.bintray-release'\n\ndef gitSha = 'git rev-parse --short HEAD"
  },
  {
    "path": "library/proguard-rules.pro",
    "chars": 667,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /A"
  },
  {
    "path": "library/publish.gradle",
    "chars": 509,
    "preview": "ext {\n    PUBLISH_GROUP_ID = 'com.malmstein'\n    PUBLISH_ARTIFACT_ID = 'fenster'\n    PUBLISH_VERSION = project.version\n\n"
  },
  {
    "path": "library/src/androidTest/java/com/malmstein/fenster/ApplicationTest.java",
    "chars": 352,
    "preview": "package com.malmstein.fenster;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "chars": 118,
    "preview": "<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",
    "chars": 402,
    "preview": "package com.malmstein.fenster.controller;\n\nimport com.malmstein.fenster.play.FensterPlayer;\n\npublic interface FensterPla"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/FensterPlayerControllerVisibilityListener.java",
    "chars": 392,
    "preview": "package com.malmstein.fenster.controller;\n\n/**\n * Called to notify that the control have been made visible or hidden.\n *"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/MediaFensterPlayerController.java",
    "chars": 19475,
    "preview": "package com.malmstein.fenster.controller;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os."
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/controller/SimpleMediaFensterPlayerController.java",
    "chars": 16569,
    "preview": "package com.malmstein.fenster.controller;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os."
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterEventsListener.java",
    "chars": 362,
    "preview": "package com.malmstein.fenster.gestures;\n\nimport android.view.MotionEvent;\n\npublic interface FensterEventsListener {\n\n   "
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterGestureControllerView.java",
    "chars": 1364,
    "preview": "package com.malmstein.fenster.gestures;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport androi"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/gestures/FensterGestureListener.java",
    "chars": 3380,
    "preview": "package com.malmstein.fenster.gestures;\n\nimport android.util.Log;\nimport android.view.GestureDetector;\nimport android.vi"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/helper/BrightnessHelper.java",
    "chars": 733,
    "preview": "package com.malmstein.fenster.helper;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport an"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterPlayer.java",
    "chars": 675,
    "preview": "package com.malmstein.fenster.play;\n\npublic interface FensterPlayer {\n    void start();\n\n    void pause();\n\n    int getD"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterVideoFragment.java",
    "chars": 2774,
    "preview": "package com.malmstein.fenster.play;\n\nimport android.app.Fragment;\nimport android.os.Bundle;\nimport android.view.LayoutIn"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/play/FensterVideoStateListener.java",
    "chars": 218,
    "preview": "package com.malmstein.fenster.play;\n\npublic interface FensterVideoStateListener {\n\n    void onFirstVideoFrameRendered();"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/seekbar/BrightnessSeekBar.java",
    "chars": 2703,
    "preview": "package com.malmstein.fenster.seekbar;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/seekbar/VolumeSeekBar.java",
    "chars": 3393,
    "preview": "package com.malmstein.fenster.seekbar;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterLoadingView.java",
    "chars": 806,
    "preview": "package com.malmstein.fenster.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.vi"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterTouchRoot.java",
    "chars": 1540,
    "preview": "package com.malmstein.fenster.view;\n\nimport android.content.Context;\nimport android.os.SystemClock;\nimport android.util."
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/FensterVideoView.java",
    "chars": 28453,
    "preview": "package com.malmstein.fenster.view;\n\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.cont"
  },
  {
    "path": "library/src/main/java/com/malmstein/fenster/view/VideoSizeCalculator.java",
    "chars": 3992,
    "preview": "package com.malmstein.fenster.view;\n\nimport android.view.SurfaceHolder;\nimport android.view.View;\n\npublic class VideoSiz"
  },
  {
    "path": "library/src/main/res/layout/fen__fragment_fenster_gesture.xml",
    "chars": 949,
    "preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/too"
  },
  {
    "path": "library/src/main/res/layout/fen__fragment_fenster_video.xml",
    "chars": 891,
    "preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:tools=\"http://schemas.android.com/too"
  },
  {
    "path": "library/src/main/res/layout/fen__view_loading.xml",
    "chars": 280,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ProgressBar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  androi"
  },
  {
    "path": "library/src/main/res/layout/fen__view_media_controller.xml",
    "chars": 3628,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  and"
  },
  {
    "path": "library/src/main/res/layout/fen__view_simple_media_controller.xml",
    "chars": 2691,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.malmstein.fenster.view.FensterTouchRoot xmlns:android=\"http://schemas.andro"
  },
  {
    "path": "library/src/main/res/values/attrs.xml",
    "chars": 292,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <attr name=\"scaleType\" format=\"enum\">\n    <enum name=\"scaleToFit\" "
  },
  {
    "path": "library/src/main/res/values/fen__colors.xml",
    "chars": 116,
    "preview": "<?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",
    "chars": 765,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <!-- PADDINGS -->\n  <dimen name=\"fen__padding_none\">0dp</dimen>\n  "
  },
  {
    "path": "library/src/main/res/values/fen__strings.xml",
    "chars": 257,
    "preview": "<resources>\n  <string name=\"fen__app_name\">Fenster</string>\n  <string name=\"fen__play_error_message\">Impossible to play "
  },
  {
    "path": "library/src/main/res/values/fen__styles.xml",
    "chars": 1031,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n  <style name=\"MediaButton\">\n    <item name=\"android:background\">@nu"
  },
  {
    "path": "library/src/main/res/values/public.xml",
    "chars": 73,
    "preview": "<resources>\n  <public name='fen__app_name' type='string' />\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "chars": 27,
    "preview": "include ':app', ':library'\n"
  },
  {
    "path": "team-props/bintray.gradle",
    "chars": 1755,
    "preview": "if (project.plugins.hasPlugin('com.android.library')) {\n    apply from: 'https://gist.githubusercontent.com/hal9002/691b"
  },
  {
    "path": "team-props/deploy.gradle",
    "chars": 1155,
    "preview": "apply plugin: 'maven'\n\ndef groupId = project.PUBLISH_GROUP_ID\ndef artifactId = project.PUBLISH_ARTIFACT_ID\ndef version ="
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the malmstein/fenster GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (148.0 KB), approximately 35.4k tokens, and a symbol index with 310 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!