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/

Install
=============================
[  ](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
}
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
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.