Skip to content

Commit

Permalink
add ability to play media from external storage by absolute file path
Browse files Browse the repository at this point in the history
  • Loading branch information
warren-bank committed Mar 22, 2020
1 parent 9da9798 commit b524c24
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 9 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ __AirPlay APIs:__
# network address for running instance of 'ExoPlayer AirPlay Receiver'
airplay_ip='192.168.1.100:8192'

# file path for test image:
# file path for test image (on sender):
image_path='/path/to/image.jpg'

# URL for test image:
Expand All @@ -83,9 +83,14 @@ __AirPlay APIs:__
audio_mp3s_m3u='https://archive.org/download/Mozart_Vesperae_Solennes_de_Confessore/Mozart%20-%20Vesper%C3%A6%20Solennes%20de%20Confessore%20%28Cooke%29.m3u'
audio_htm_page='https://archive.org/details/tntvillage_455310'
audio_mp3s_htm='https://archive.org/download/tntvillage_455310/S%26G/Live/1967%20-%20Live%20From%20New%20York%20City%20%40320/'

# file paths for test media (on receiver):
video_path='/storage/external_SD/video/file.mp4'
subtt_path='/storage/external_SD/video/file.srt'
audio_path='/storage/external_SD/audio/file.mp3'
```

* display image from local file system:
* display image from local file system (on sender):
```bash
curl --silent -X POST \
--data-binary "@${image_path}" \
Expand Down Expand Up @@ -224,6 +229,21 @@ __extended APIs:__
--data-binary "Content-Location: ${audio_mp3s_htm}\nReferer: ${audio_htm_page}\nStart-Position: 0" \
"http://${airplay_ip}/queue"
```
* play video from file system on receiver (add text captions, seek to beginning):
```bash
curl --silent -X POST \
-H "Content-Type: text/parameters" \
--data-binary "Content-Location: ${video_path}\nCaption-Location: ${subtt_path}\nStart-Position: 0" \
"http://${airplay_ip}/play"
```
* add audio from file system on receiver to end of queue (seek to 50%):
```bash
# note: position < 1 is a percent of the total track length
curl --silent -X POST \
-H "Content-Type: text/parameters" \
--data-binary "Content-Location: ${audio_path}\nStart-Position: 0.5" \
"http://${airplay_ip}/queue"
```

#### Usage (high level):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.github.warren_bank.exoplayer_airplay_receiver.R;
import com.github.warren_bank.exoplayer_airplay_receiver.ui.exoplayer2.customizations.MyRenderersFactory;
import com.github.warren_bank.exoplayer_airplay_receiver.ui.exoplayer2.customizations.TextSynchronizer;
import com.github.warren_bank.exoplayer_airplay_receiver.utils.ExternalStorageUtils;
import com.github.warren_bank.exoplayer_airplay_receiver.utils.SystemUtils;

import android.content.Context;
Expand Down Expand Up @@ -37,6 +38,7 @@
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
Expand Down Expand Up @@ -796,25 +798,33 @@ private MediaSource buildMediaSource(VideoSource sample) {
}

private MediaSource buildUriMediaSource(VideoSource sample) {
if (httpDataSourceFactory == null)
DataSource.Factory factory = ExternalStorageUtils.isFileUri(sample.uri)
? rawDataSourceFactory
: httpDataSourceFactory;

if (factory == null)
return null;

Uri uri = Uri.parse(sample.uri);

switch (sample.uri_mimeType) {
case MimeTypes.APPLICATION_M3U8:
return new HlsMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri);
return new HlsMediaSource.Factory(factory).createMediaSource(uri);
case MimeTypes.APPLICATION_MPD:
return new DashMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri);
return new DashMediaSource.Factory(factory).createMediaSource(uri);
case MimeTypes.APPLICATION_SS:
return new SsMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri);
return new SsMediaSource.Factory(factory).createMediaSource(uri);
default:
return new ExtractorMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri);
return new ExtractorMediaSource.Factory(factory).createMediaSource(uri);
}
}

private MediaSource buildCaptionMediaSource(VideoSource sample) {
if (httpDataSourceFactory == null)
DataSource.Factory factory = ExternalStorageUtils.isFileUri(sample.caption)
? rawDataSourceFactory
: httpDataSourceFactory;

if (factory == null)
return null;

if ((sample.caption == null) || (sample.caption_mimeType == null))
Expand All @@ -823,7 +833,7 @@ private MediaSource buildCaptionMediaSource(VideoSource sample) {
Uri uri = Uri.parse(sample.caption);
Format format = Format.createTextSampleFormat(/* id= */ null, sample.caption_mimeType, /* selectionFlags= */ C.SELECTION_FLAG_DEFAULT, /* language= */ "en");

return new SingleSampleMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri, format, C.TIME_UNSET);
return new SingleSampleMediaSource.Factory(factory).createMediaSource(uri, format, C.TIME_UNSET);
}

private MediaSource buildRawVideoMediaSource(int rawResourceId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.github.warren_bank.exoplayer_airplay_receiver.ui.exoplayer2;

import com.github.warren_bank.exoplayer_airplay_receiver.R;
import com.github.warren_bank.exoplayer_airplay_receiver.utils.ExternalStorageUtils;
import com.github.warren_bank.exoplayer_airplay_receiver.utils.SystemUtils;
import com.github.warren_bank.exoplayer_airplay_receiver.utils.WakeLockMgr;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
Expand All @@ -19,9 +21,13 @@
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;

import java.util.ArrayList;

public class VideoActivity extends AppCompatActivity implements PlayerControlView.VisibilityListener, View.OnClickListener {
private static final String tag = "VideoActivity";

private final ArrayList<Intent> externalStorageIntents = new ArrayList<Intent>();

private PlayerView playerView;
private Button selectTracksButton;
private Button selectTextOffsetButton;
Expand Down Expand Up @@ -67,6 +73,15 @@ public void onNewIntent(Intent intent) {
handleIntent(intent);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

if (ExternalStorageUtils.is_permission_granted(requestCode, grantResults)) {
handleExternalStorageIntents();
}
}

@Override
protected void onStop() {
super.onStop();
Expand Down Expand Up @@ -187,6 +202,21 @@ public void onClick(View view) {

// Internal methods.

private void handleExternalStorageIntents() {
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (!externalStorageIntents.isEmpty()) {
handleIntent(externalStorageIntents.remove(0));
handler.postDelayed(this, 1000l);
}
}
};

handler.post(runnable);
}

private void handleIntent(Intent intent) {
if (intent == null)
return;
Expand Down Expand Up @@ -215,6 +245,21 @@ private void handleIntent(Intent intent) {
if (uri == null)
return;

boolean requiresExternalStoragePermission = false;
if (ExternalStorageUtils.isFileUri(uri)) {
uri = ExternalStorageUtils.normalizeFileUri(uri);
requiresExternalStoragePermission = true;
}
if (ExternalStorageUtils.isFileUri(caption)) {
uri = ExternalStorageUtils.normalizeFileUri(caption);
requiresExternalStoragePermission = true;
}
if (requiresExternalStoragePermission && !ExternalStorageUtils.has_permission(this)) {
externalStorageIntents.add(intent);
ExternalStorageUtils.request_permission(this);
return;
}

if ((mode != null) && mode.equals("queue")) {
playerManager.AirPlay_queue(uri, caption, referer, startPosition);
Log.d(tag, "queue video: url = " + uri + "; position = " + startPosition + "; captions = " + caption + "; referer = " + referer);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.github.warren_bank.exoplayer_airplay_receiver.utils;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;

import java.io.File;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class ExternalStorageUtils {

private static Pattern file_uri_regex = Pattern.compile("^(?:/|file:/)");

public static boolean isFileUri(String uri) {
if (uri == null) return false;

Matcher matcher = ExternalStorageUtils.file_uri_regex.matcher(uri.toLowerCase());
return matcher.find();
}

public static String normalizeFileUri(String uri) {
if (uri == null) return null;
if (uri.isEmpty()) return null;

if (uri.charAt(0) == '/')
uri = (new File(uri)).toURI().toString();

return uri;
}

public static boolean has_permission(Activity activity) {
if (Build.VERSION.SDK_INT < 23) {
return true;
} else {
String permission = Manifest.permission.READ_EXTERNAL_STORAGE;

return (activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
}
}

private static final int PERMISSIONS_REQUEST_CODE = 0;

public static void request_permission(Activity activity) {
String permission = Manifest.permission.READ_EXTERNAL_STORAGE;

activity.requestPermissions(new String[]{permission}, ExternalStorageUtils.PERMISSIONS_REQUEST_CODE);
}

public static boolean is_permission_granted(int requestCode, int[] grantResults) {
return (
(requestCode == ExternalStorageUtils.PERMISSIONS_REQUEST_CODE)
&& (grantResults.length == 1)
&& (grantResults[0] == PackageManager.PERMISSION_GRANTED)
);
}

}

0 comments on commit b524c24

Please sign in to comment.