Added support for playing local external media files

This commit is contained in:
daniel oeh 2013-02-27 22:05:13 +01:00
parent cc741d6643
commit dcbf334bad
10 changed files with 385 additions and 82 deletions

View File

@ -74,7 +74,17 @@
android:name=".activity.AudioplayerActivity" android:name=".activity.AudioplayerActivity"
android:configChanges="keyboardHidden|orientation" android:configChanges="keyboardHidden|orientation"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="portrait" android:label=" "/> android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:mimeType="audio/*" />
</intent-filter>
</activity>
<service <service
android:name=".service.download.DownloadService" android:name=".service.download.DownloadService"
@ -323,7 +333,11 @@
<activity <activity
android:name=".activity.DirectoryChooserActivity" android:name=".activity.DirectoryChooserActivity"
android:label="@string/choose_data_directory" /> android:label="@string/choose_data_directory" />
<activity android:label="@string/organize_queue_label" android:name=".activity.OrganizeQueueActivity" android:configChanges="orientation"></activity> <activity
android:name=".activity.OrganizeQueueActivity"
android:configChanges="orientation"
android:label="@string/organize_queue_label" >
</activity>
</application> </application>
</manifest> </manifest>

View File

@ -1,5 +1,7 @@
package de.danoeh.antennapod.activity; package de.danoeh.antennapod.activity;
import java.io.File;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.os.Bundle; import android.os.Bundle;
@ -22,10 +24,12 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter; import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.Playable;
/** Activity for playing audio files. */ /** Activity for playing audio files. */
@ -71,9 +75,31 @@ public class AudioplayerActivity extends MediaplayerActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowTitleEnabled(false);
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS]; detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
} }
@Override
protected void onResume() {
super.onResume();
if (getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
Intent intent = getIntent();
if (AppConfig.DEBUG)
Log.d(TAG, "Received VIEW intent: "
+ intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(), MediaType.AUDIO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
}
}
@Override @Override
protected void onAwaitingVideoSurface() { protected void onAwaitingVideoSurface() {
startActivity(new Intent(this, VideoplayerActivity.class)); startActivity(new Intent(this, VideoplayerActivity.class));
@ -193,8 +219,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
@Override @Override
public void run() { public void run() {
ImageLoader.getInstance().loadThumbnailBitmap( ImageLoader.getInstance().loadThumbnailBitmap(
media.getImageFileUrl(), media.getImageFileUrl(), butNavLeft);
butNavLeft);
} }
}); });
butNavRight.setImageDrawable(drawables.getDrawable(0)); butNavRight.setImageDrawable(drawables.getDrawable(0));

View File

@ -238,18 +238,15 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
Playable media = controller.getMedia(); Playable media = controller.getMedia();
if (media == null) { if (item.getItemId() == android.R.id.home) {
return false;
}
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(MediaplayerActivity.this, Intent intent = new Intent(MediaplayerActivity.this,
MainActivity.class); MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK); | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); startActivity(intent);
break; return true;
} else if (media != null) {
switch (item.getItemId()) {
case R.id.disable_sleeptimer_item: case R.id.disable_sleeptimer_item:
if (controller.serviceAvailable()) { if (controller.serviceAvailable()) {
AlertDialog.Builder stDialog = new AlertDialog.Builder(this); AlertDialog.Builder stDialog = new AlertDialog.Builder(this);
@ -257,7 +254,8 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
stDialog.setMessage(getString(R.string.time_left_label) stDialog.setMessage(getString(R.string.time_left_label)
+ Converter.getDurationStringLong((int) controller + Converter.getDurationStringLong((int) controller
.getSleepTimerTimeLeft())); .getSleepTimerTimeLeft()));
stDialog.setPositiveButton(R.string.disable_sleeptimer_label, stDialog.setPositiveButton(
R.string.disable_sleeptimer_label,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
@ -310,6 +308,9 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
} }
return true; return true;
} else {
return false;
}
} }
@Override @Override

View File

@ -187,7 +187,7 @@ public class FeedMedia extends FeedFile implements Playable {
@Override @Override
public void loadMetadata() throws PlayableException { public void loadMetadata() throws PlayableException {
if (getChapters() == null) { if (getChapters() == null) {
ChapterUtils.loadChapters(this); ChapterUtils.loadChaptersFromStreamUrl(this);
} }
} }

View File

@ -185,8 +185,7 @@ public class PlaybackService extends Service {
* Same as getPlayerActivityIntent(context), but here the type of activity * Same as getPlayerActivityIntent(context), but here the type of activity
* depends on the FeedMedia that is provided as an argument. * depends on the FeedMedia that is provided as an argument.
*/ */
public static Intent getPlayerActivityIntent(Context context, public static Intent getPlayerActivityIntent(Context context, Playable media) {
Playable media) {
MediaType mt = media.getMediaType(); MediaType mt = media.getMediaType();
if (mt == MediaType.VIDEO) { if (mt == MediaType.VIDEO) {
return new Intent(context, VideoplayerActivity.class); return new Intent(context, VideoplayerActivity.class);
@ -539,17 +538,23 @@ public class PlaybackService extends Service {
} else if (media.localFileAvailable()) { } else if (media.localFileAvailable()) {
player.setDataSource(media.getFileUrl()); player.setDataSource(media.getFileUrl());
} }
} catch (IOException e) {
e.printStackTrace();
}
if (prepareImmediately) { if (prepareImmediately) {
setStatus(PlayerStatus.PREPARING); setStatus(PlayerStatus.PREPARING);
player.prepareAsync(); player.prepareAsync();
} else { } else {
setStatus(PlayerStatus.INITIALIZED); setStatus(PlayerStatus.INITIALIZED);
} }
} catch (IOException e) {
e.printStackTrace();
media = null;
setStatus(PlayerStatus.ERROR);
sendBroadcast(new Intent(
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
}
} else { } else {
Log.e(TAG, "InitTask could not load metadata"); Log.e(TAG, "InitTask could not load metadata");
media = null;
setStatus(PlayerStatus.ERROR); setStatus(PlayerStatus.ERROR);
sendBroadcast(new Intent( sendBroadcast(new Intent(
ACTION_SHUTDOWN_PLAYBACK_SERVICE)); ACTION_SHUTDOWN_PLAYBACK_SERVICE));
@ -801,7 +806,11 @@ public class PlaybackService extends Service {
public void stop() { public void stop() {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Stopping playback"); Log.d(TAG, "Stopping playback");
if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED
|| status == PlayerStatus.STOPPED
|| status == PlayerStatus.PLAYING) {
player.stop(); player.stop();
}
setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
stopSelf(); stopSelf();
} }

View File

@ -83,7 +83,7 @@ public class PlayerWidgetService extends Service {
PlaybackService.getPlayerActivityIntent(this), 0); PlaybackService.getPlayerActivityIntent(this), 0);
views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
if (playbackService != null) { if (playbackService != null && playbackService.getMedia() != null) {
Playable media = playbackService.getMedia(); Playable media = playbackService.getMedia();
PlayerStatus status = playbackService.getStatus(); PlayerStatus status = playbackService.getStatus();

View File

@ -833,10 +833,7 @@ public class DownloadService extends Service {
} }
if (media.getItem().getChapters() == null) { if (media.getItem().getChapters() == null) {
ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); ChapterUtils.loadChaptersFromFileUrl(media);
if (media.getItem().getChapters() == null) {
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
}
if (media.getItem().getChapters() != null) { if (media.getItem().getChapters() != null) {
chaptersRead = true; chaptersRead = true;
} }

View File

@ -242,7 +242,7 @@ public class ChapterUtils {
} }
} }
public static void loadChapters(Playable media) { public static void loadChaptersFromStreamUrl(Playable media) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Starting chapterLoader thread"); Log.d(TAG, "Starting chapterLoader thread");
ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media); ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media);
@ -253,4 +253,15 @@ public class ChapterUtils {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "ChapterLoaderThread has finished"); Log.d(TAG, "ChapterLoaderThread has finished");
} }
public static void loadChaptersFromFileUrl(Playable media) {
if (media.localFileAvailable()) {
ChapterUtils.readID3ChaptersFromPlayableFileUrl(media);
if (media.getChapters() == null) {
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
}
} else {
Log.e(TAG, "Could not load chapters from file url: local file not available");
}
}
} }

View File

@ -0,0 +1,238 @@
package de.danoeh.antennapod.util.playback;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import org.apache.commons.io.IOUtils;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.MediaMetadataRetriever;
import android.os.Parcel;
import android.os.Parcelable;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.FileNameGenerator;
/** Represents a media file that is stored on the local storage device. */
public class ExternalMedia implements Playable {
public static final int PLAYABLE_TYPE_EXTERNAL_MEDIA = 2;
public static final String PREF_SOURCE_URL = "ExternalMedia.PrefSourceUrl";
public static final String PREF_POSITION = "ExternalMedia.PrefPosition";
public static final String PREF_MEDIA_TYPE = "ExternalMedia.PrefMediaType";
private String source;
private String episodeTitle;
private String feedTitle;
private String shownotes;
private MediaType mediaType = MediaType.AUDIO;
private List<Chapter> chapters;
private String imageUrl;
private int duration;
private int position;
public ExternalMedia(String source, MediaType mediaType) {
super();
this.source = source;
this.mediaType = mediaType;
}
public ExternalMedia(String source, MediaType mediaType, int position) {
this(source, mediaType);
this.position = position;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(source);
dest.writeString(mediaType.toString());
dest.writeInt(position);
}
@Override
public void writeToPreferences(Editor prefEditor) {
prefEditor.putString(PREF_SOURCE_URL, source);
prefEditor.putString(PREF_MEDIA_TYPE, mediaType.toString());
prefEditor.putInt(PREF_POSITION, position);
}
@Override
public void loadMetadata() throws PlayableException {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try {
mmr.setDataSource(source);
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new PlayableException("IllegalArgumentException when setting up MediaMetadataReceiver");
}
episodeTitle = mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
feedTitle = mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
duration = Integer.parseInt(mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
ChapterUtils.loadChaptersFromFileUrl(this);
byte[] imgData = mmr.getEmbeddedPicture();
File cacheDir = PodcastApp.getInstance().getExternalCacheDir();
if (cacheDir != null) {
OutputStream out = null;
try {
File tmpFile = File.createTempFile(
FileNameGenerator.generateFileName(source) + "-img",
null, cacheDir);
out = new BufferedOutputStream(new FileOutputStream(tmpFile));
IOUtils.write(imgData, out);
imageUrl = tmpFile.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
throw new PlayableException("IOException during loadMetadata()");
} finally {
IOUtils.closeQuietly(out);
}
}
}
@Override
public String getEpisodeTitle() {
return episodeTitle;
}
@Override
public void loadShownotes(ShownoteLoaderCallback callback) {
callback.onShownotesLoaded(null);
}
@Override
public List<Chapter> getChapters() {
return chapters;
}
@Override
public String getWebsiteLink() {
return null;
}
@Override
public String getPaymentLink() {
return null;
}
@Override
public String getFeedTitle() {
return feedTitle;
}
@Override
public String getImageFileUrl() {
return imageUrl;
}
@Override
public Object getIdentifier() {
return source;
}
@Override
public int getDuration() {
return duration;
}
@Override
public int getPosition() {
return position;
}
@Override
public MediaType getMediaType() {
return mediaType;
}
@Override
public String getFileUrl() {
return source;
}
@Override
public String getStreamUrl() {
return null;
}
@Override
public boolean localFileAvailable() {
return true;
}
@Override
public boolean streamAvailable() {
return false;
}
@Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
SharedPreferences.Editor editor = pref.edit();
editor.putInt(PREF_POSITION, newPosition);
position = newPosition;
editor.commit();
}
@Override
public void setPosition(int newPosition) {
position = newPosition;
}
@Override
public void setDuration(int newDuration) {
duration = newDuration;
}
@Override
public void onPlaybackStart() {
}
@Override
public void onPlaybackCompleted() {
}
@Override
public int getPlayableType() {
return PLAYABLE_TYPE_EXTERNAL_MEDIA;
}
@Override
public void setChapters(List<Chapter> chapters) {
this.chapters = chapters;
}
public static final Parcelable.Creator<ExternalMedia> CREATOR = new Parcelable.Creator<ExternalMedia>() {
public ExternalMedia createFromParcel(Parcel in) {
String source = in.readString();
MediaType type = MediaType.valueOf(in.readString());
int position = 0;
if (in.dataAvail() > 0) {
position = in.readInt();
}
ExternalMedia extMedia = new ExternalMedia(source, type, position);
return extMedia;
}
public ExternalMedia[] newArray(int size) {
return new ExternalMedia[size];
}
};
}

View File

@ -150,6 +150,14 @@ public interface Playable extends Parcelable {
} }
} }
break; break;
case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
String source = pref.getString(ExternalMedia.PREF_SOURCE_URL, null);
String mediaType = pref.getString(ExternalMedia.PREF_MEDIA_TYPE, null);
if (source != null && mediaType != null) {
int position = pref.getInt(ExternalMedia.PREF_POSITION, 0);
return new ExternalMedia(source,MediaType.valueOf(mediaType), position);
}
break;
} }
Log.e(TAG, "Could not restore Playable object from preferences"); Log.e(TAG, "Could not restore Playable object from preferences");
return null; return null;