-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
v1.0.0 - initial commit and public release
- Loading branch information
0 parents
commit 2bd894d
Showing
42 changed files
with
4,577 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Android generated | ||
bin | ||
gen | ||
obj | ||
lint.xml | ||
|
||
# IntelliJ IDEA | ||
.idea | ||
*.iml | ||
*.ipr | ||
*.iws | ||
classes | ||
gen-external-apklibs | ||
|
||
# Gradle | ||
.gradle | ||
build | ||
buildout | ||
out | ||
|
||
# Other | ||
.DS_Store | ||
local.properties |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
#### [ExoPlayer AirPlay Receiver](https://github.com/warren-bank/Android-ExoPlayer-AirPlay-Receiver) | ||
##### (less formally named: _"ExoAirPlayer"_) | ||
|
||
Android app to run on a set-top box and play video URLs "cast" to it with a stateless HTTP API (based on AirPlay). | ||
|
||
- - - - | ||
|
||
#### Background: | ||
|
||
* I use Chromecasts _a lot_ | ||
- they are incredibly adaptable | ||
* though their protocol is [proprietary and locked down](https://blog.oakbits.com/google-cast-protocol-receiver-authentication.html) | ||
- though the [Google Cast SDK for Android](https://developers.google.com/cast/docs/android_sender) is nearly ubiquitous, I very rarely cast video from apps | ||
- I find much better video content to stream on websites, and wrote some tools to identify and cast these URLs | ||
* [Chrome extension](https://github.com/warren-bank/crx-webcast-reloaded) to use with desktop web browsers | ||
* [Android app](https://github.com/warren-bank/Android-WebCast) to use with mobile devices | ||
* I also really like using Android set-top boxes | ||
- mainly to play video files stored on an attached drive | ||
- they are incredibly adaptable | ||
* able to run any Android apk, such as: | ||
- VPN client | ||
- torrent client | ||
- FTP client | ||
- HTTP server | ||
* I thought it would be "fun" to write an app to run on Android set-top boxes that could provide the same functionality that I enjoy on Chromecasts | ||
- and will work equally well on smaller screens (ex: phones and tablets) | ||
|
||
#### Scope: | ||
|
||
* the goal is __not__ to provide an app that is recognized on the LAN as a virtual Chromecast device | ||
- [CheapCast](https://github.com/mauimauer/cheapcast) accomplished this in 2013 | ||
* Google quickly [changed its protocol](https://blog.oakbits.com/google-cast-protocol-discovery-and-connection.html) | ||
* AirPlay uses a very simple stateless [HTTP API](http://nto.github.io/AirPlay.html#video) | ||
- this is a great starting point | ||
* it supports: play, pause, seek, stop | ||
- I'd like to extend this API (for a custom sender) | ||
* to add support for: video queue, next, previous, mute, set volume | ||
|
||
#### Design: | ||
|
||
* [ExoPlayer](https://github.com/google/ExoPlayer) | ||
- media player used to render video URLs | ||
* [HttpCore](http://hc.apache.org/httpcomponents-core-ga/) | ||
- low level HTTP transport components used to build a custom HTTP service | ||
* [jmDNS](https://github.com/jmdns/jmdns) | ||
- multi-cast DNS service registration used to make AirPlay-compatible HTTP service discoverable on LAN | ||
|
||
- - - - | ||
|
||
#### Usage (low level): | ||
|
||
__AirPlay APIs:__ | ||
|
||
```bash | ||
# network address for running instance of 'ExoPlayer AirPlay Receiver' | ||
airplay_ip='192.168.1.100:8192' | ||
|
||
# URLs for test videos: | ||
videos_page='https://players.akamai.com/hls/' | ||
video_url_1='https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8' | ||
video_url_2='https://multiplatform-f.akamaihd.net/i/multi/april11/hdworld/hdworld_,512x288_450_b,640x360_700_b,768x432_1000_b,1024x576_1400_m,.mp4.csmil/master.m3u8' | ||
video_url_3='https://multiplatform-f.akamaihd.net/i/multi/april11/cctv/cctv_,512x288_450_b,640x360_700_b,768x432_1000_b,1024x576_1400_m,.mp4.csmil/master.m3u8' | ||
``` | ||
|
||
* play video #1: | ||
```bash | ||
curl -X POST \ | ||
-H "Content-Type: text/parameters" \ | ||
--data-binary "Content-Location: ${video_url_1}\nStart-Position: 0" \ | ||
"http://${airplay_ip}/play" | ||
``` | ||
* seek to `30 seconds` within currently playing video: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/scrub?position=30.0" | ||
``` | ||
* pause the currently playing video: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/rate?value=0.0" | ||
``` | ||
* resume playback of the currently paused video: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/rate?value=1.0" | ||
``` | ||
* increase speed of playback to 10x: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/rate?value=10.0" | ||
``` | ||
* stop playback: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/stop" | ||
``` | ||
|
||
__extended APIs:__ | ||
|
||
* add video #2 to end of queue (set 'Referer' request header, seek to 50%): | ||
```bash | ||
# note: position < 1 is a percent of the total video length | ||
curl -X POST \ | ||
-H "Content-Type: text/parameters" \ | ||
--data-binary "Content-Location: ${video_url_2}\nReferer: ${videos_page}\nStart-Position: 0.5" \ | ||
"http://${airplay_ip}/queue" | ||
``` | ||
* add video #3 to end of queue (set 'Referer' request header, seek to 30 seconds): | ||
```bash | ||
# note: position >= 1 is a fixed offset (in seconds) | ||
curl -X POST \ | ||
-H "Content-Type: text/parameters" \ | ||
--data-binary "Content-Location: ${video_url_3}\nReferer: ${videos_page}\nStart-Position: 30" \ | ||
"http://${airplay_ip}/queue" | ||
``` | ||
* skip forward to next video in queue: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/next" | ||
``` | ||
* skip backward to previous video in queue: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/previous" | ||
``` | ||
* mute audio: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/volume?value=0.0" | ||
``` | ||
* set audio volume to 50%: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/volume?value=0.5" | ||
``` | ||
* set audio volume to 100%: | ||
```bash | ||
# note: POST body is required, even when it contains no data | ||
curl -X POST \ | ||
--data-binary "" \ | ||
"http://${airplay_ip}/volume?value=1.0" | ||
``` | ||
|
||
#### Usage (high level): | ||
|
||
__to do:__ | ||
|
||
* update [Chrome extension](https://github.com/warren-bank/crx-webcast-reloaded) to support "casting" videos from websites to a running instance of [ExoPlayer AirPlay Receiver](https://github.com/warren-bank/Android-ExoPlayer-AirPlay-Receiver) | ||
|
||
- - - - | ||
|
||
#### Credits: | ||
|
||
* [AirPlay-Receiver-on-Android](https://github.com/gpfduoduo/AirPlay-Receiver-on-Android) | ||
- __brilliant__ | ||
- what I like: | ||
* quality of code is excellent | ||
* implements 99% of what I've described | ||
- [media player](https://github.com/yixia/VitamioBundle) used to render video URLs | ||
- _HttpCore_ web server that implements all _AirPlay_ video APIs | ||
- _jmDNS_ Bonjour registration | ||
- what I dislike: | ||
* all libraries are 5 years old | ||
* doesn't use _ExoPlayer_ | ||
* the repo includes a lot of unused code | ||
- needs a little housekeeping | ||
|
||
- - - - | ||
|
||
#### Legal: | ||
|
||
* copyright: [Warren Bank](https://github.com/warren-bank) | ||
* license: [GPL-2.0](https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt) |
58 changes: 58 additions & 0 deletions
58
android-studio-project/ExoPlayer-AirPlay-Receiver/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
apply from: '../constants.gradle' | ||
apply plugin: 'com.android.application' | ||
|
||
android { | ||
compileSdkVersion project.ext.compileSdkVersion | ||
buildToolsVersion project.ext.buildToolsVersion | ||
|
||
compileOptions { | ||
sourceCompatibility project.ext.javaVersion | ||
targetCompatibility project.ext.javaVersion | ||
} | ||
|
||
defaultConfig { | ||
minSdkVersion project.ext.minSdkVersion | ||
targetSdkVersion project.ext.targetSdkVersion | ||
|
||
applicationId "com.github.warren_bank.exoplayer_airplay_receiver" | ||
versionName project.ext.releaseVersion | ||
versionCode project.ext.releaseVersionCode | ||
} | ||
|
||
buildTypes { | ||
release { | ||
shrinkResources true | ||
minifyEnabled true | ||
proguardFiles = [ | ||
"proguard-rules.txt", | ||
getDefaultProguardFile('proguard-android.txt') | ||
] | ||
} | ||
debug { | ||
debuggable true | ||
jniDebuggable true | ||
} | ||
} | ||
|
||
lintOptions { | ||
disable 'MissingTranslation' | ||
abortOnError true | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly 'androidx.annotation:annotation:' + project.ext.libVersionAndroidX // ( 27 KB) https://mvnrepository.com/artifact/androidx.annotation/annotation | ||
annotationProcessor 'androidx.annotation:annotation:' + project.ext.libVersionAndroidX | ||
|
||
implementation 'com.google.android.exoplayer:exoplayer-core:' + project.ext.libVersionExoPlayer // (1.3 MB) https://mvnrepository.com/artifact/com.google.android.exoplayer/exoplayer-core?repo=bt-google-exoplayer | ||
implementation 'com.google.android.exoplayer:exoplayer-dash:' + project.ext.libVersionExoPlayer // (107 KB) https://mvnrepository.com/artifact/com.google.android.exoplayer/exoplayer-dash?repo=bt-google-exoplayer | ||
implementation 'com.google.android.exoplayer:exoplayer-hls:' + project.ext.libVersionExoPlayer // ( 98 KB) https://mvnrepository.com/artifact/com.google.android.exoplayer/exoplayer-hls?repo=bt-google-exoplayer | ||
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:' + project.ext.libVersionExoPlayer // ( 44 KB) https://mvnrepository.com/artifact/com.google.android.exoplayer/exoplayer-smoothstreaming?repo=bt-google-exoplayer | ||
implementation 'com.google.android.exoplayer:exoplayer-ui:' + project.ext.libVersionExoPlayer // (238 KB) https://mvnrepository.com/artifact/com.google.android.exoplayer/exoplayer-ui?repo=bt-google-exoplayer | ||
|
||
implementation 'com.googlecode.plist:dd-plist:' + project.ext.libVersionDdPlist // ( 74 KB) https://mvnrepository.com/artifact/com.googlecode.plist/dd-plist | ||
implementation 'org.apache.httpcomponents:httpcore:' + project.ext.libVersionHttpCore // (320 KB) https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore | ||
implementation 'org.jmdns:jmdns:' + project.ext.libVersionJmDNS // (209 KB) https://mvnrepository.com/artifact/org.jmdns/jmdns | ||
} | ||
|
||
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' |
10 changes: 10 additions & 0 deletions
10
android-studio-project/ExoPlayer-AirPlay-Receiver/proguard-rules.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
-keep class com.github.warren_bank.exoplayer_airplay_receiver.** { *; } | ||
|
||
-keep class com.google.android.exoplayer.** { *; } | ||
-keep class com.google.android.exoplayer2.** { *; } | ||
|
||
-keep class javax.jmdns.** { *; } | ||
|
||
-dontwarn javax.jmdns.test.** | ||
-dontwarn org.apache.** | ||
-dontwarn org.slf4j.** |
53 changes: 53 additions & 0 deletions
53
android-studio-project/ExoPlayer-AirPlay-Receiver/src/main/AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest | ||
xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
package="com.github.warren_bank.exoplayer_airplay_receiver"> | ||
|
||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | ||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> | ||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> | ||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> | ||
<uses-permission android:name="android.permission.INTERNET" /> | ||
<uses-permission android:name="android.permission.WAKE_LOCK" /> | ||
|
||
<application | ||
android:name=".MainApp" | ||
android:label="@string/app_name" | ||
android:icon="@drawable/launcher" | ||
android:largeHeap="true" | ||
android:allowBackup="false" | ||
android:supportsRtl="false"> | ||
|
||
<service | ||
android:name=".service.NetworkingService" | ||
android:enabled="true" | ||
android:exported="true" /> | ||
|
||
<activity | ||
android:name=".ui.StartNetworkingServiceActivity" | ||
android:theme="@android:style/Theme.NoDisplay" | ||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" | ||
android:launchMode="singleTop"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
|
||
<activity | ||
android:name=".ui.ImageActivity" | ||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" | ||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" | ||
android:launchMode="singleTask" /> | ||
|
||
<activity | ||
android:name=".ui.VideoPlayerActivity" | ||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" | ||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" | ||
android:launchMode="singleTask" /> | ||
|
||
</application> | ||
|
||
</manifest> |
41 changes: 41 additions & 0 deletions
41
...lay-Receiver/src/main/java/com/github/warren_bank/exoplayer_airplay_receiver/MainApp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.github.warren_bank.exoplayer_airplay_receiver; | ||
|
||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import android.app.Application; | ||
import android.os.Handler; | ||
import android.os.Message; | ||
|
||
public class MainApp extends Application { | ||
private static MainApp instance; | ||
|
||
private ConcurrentHashMap<String, Handler> mHandlerMap = new ConcurrentHashMap<String, Handler>(); | ||
|
||
public static MainApp getInstance() { | ||
return instance; | ||
} | ||
|
||
public static void registerHandler(String name, Handler handler) { | ||
getInstance().getHandlerMap().put(name, handler); | ||
} | ||
|
||
public static void unregisterHandler(String name) { | ||
getInstance().getHandlerMap().remove(name); | ||
} | ||
|
||
public static void broadcastMessage(Message msg) { | ||
for (Handler handler : getInstance().getHandlerMap().values()) { | ||
handler.sendMessage(Message.obtain(msg)); | ||
} | ||
} | ||
|
||
@Override | ||
public void onCreate() { | ||
super.onCreate(); | ||
instance = this; | ||
} | ||
|
||
public ConcurrentHashMap<String, Handler> getHandlerMap() { | ||
return mHandlerMap; | ||
} | ||
} |
Oops, something went wrong.