Search in sources :

Example 1 with Playable

use of de.danoeh.antennapod.core.util.playback.Playable in project AntennaPod by AntennaPod.

the class CastUtils method convertFromFeedMedia.

/**
     * Converts {@link FeedMedia} objects into a format suitable for sending to a Cast Device.
     * Before using this method, one should make sure {@link #isCastable(Playable)} returns
     * {@code true}.
     *
     * Unless media.{@link FeedMedia#loadMetadata() loadMetadata()} has already been called,
     * this method should not run on the main thread.
     *
     * @param media The {@link FeedMedia} object to be converted.
     * @return {@link MediaInfo} object in a format proper for casting.
     */
public static MediaInfo convertFromFeedMedia(FeedMedia media) {
    if (media == null) {
        return null;
    }
    MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
    try {
        media.loadMetadata();
    } catch (Playable.PlayableException e) {
        Log.e(TAG, "Unable to load FeedMedia metadata", e);
    }
    FeedItem feedItem = media.getItem();
    if (feedItem != null) {
        metadata.putString(MediaMetadata.KEY_TITLE, media.getEpisodeTitle());
        String subtitle = media.getFeedTitle();
        if (subtitle != null) {
            metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle);
        }
        FeedImage image = feedItem.getImage();
        if (image != null && !TextUtils.isEmpty(image.getDownload_url())) {
            metadata.addImage(new WebImage(Uri.parse(image.getDownload_url())));
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(media.getItem().getPubDate());
        metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar);
        Feed feed = feedItem.getFeed();
        if (feed != null) {
            if (!TextUtils.isEmpty(feed.getAuthor())) {
                metadata.putString(MediaMetadata.KEY_ARTIST, feed.getAuthor());
            }
            if (!TextUtils.isEmpty(feed.getDownload_url())) {
                metadata.putString(KEY_FEED_URL, feed.getDownload_url());
            }
            if (!TextUtils.isEmpty(feed.getLink())) {
                metadata.putString(KEY_FEED_WEBSITE, feed.getLink());
            }
        }
        if (!TextUtils.isEmpty(feedItem.getItemIdentifier())) {
            metadata.putString(KEY_EPISODE_IDENTIFIER, feedItem.getItemIdentifier());
        } else {
            metadata.putString(KEY_EPISODE_IDENTIFIER, media.getStreamUrl());
        }
        if (!TextUtils.isEmpty(feedItem.getLink())) {
            metadata.putString(KEY_EPISODE_LINK, feedItem.getLink());
        }
    }
    String notes = null;
    try {
        notes = media.loadShownotes().call();
    } catch (Exception e) {
        Log.e(TAG, "Unable to load FeedMedia notes", e);
    }
    if (notes != null) {
        if (notes.length() > EPISODE_NOTES_MAX_LENGTH) {
            notes = notes.substring(0, EPISODE_NOTES_MAX_LENGTH);
        }
        metadata.putString(KEY_EPISODE_NOTES, notes);
    }
    // This field only identifies the id on the device that has the original version.
    // Idea is to perhaps, on a first approach, check if the version on the local DB with the
    // same id matches the remote object, and if not then search for episode and feed identifiers.
    // This at least should make media recognition for a single device much quicker.
    metadata.putInt(KEY_MEDIA_ID, ((Long) media.getIdentifier()).intValue());
    // A way to identify different casting media formats in case we change it in the future and
    // senders with different versions share a casting device.
    metadata.putInt(KEY_FORMAT_VERSION, FORMAT_VERSION_VALUE);
    MediaInfo.Builder builder = new MediaInfo.Builder(media.getStreamUrl()).setContentType(media.getMime_type()).setStreamType(MediaInfo.STREAM_TYPE_BUFFERED).setMetadata(metadata);
    if (media.getDuration() > 0) {
        builder.setStreamDuration(media.getDuration());
    }
    return builder.build();
}
Also used : FeedImage(de.danoeh.antennapod.core.feed.FeedImage) MediaInfo(com.google.android.gms.cast.MediaInfo) FeedItem(de.danoeh.antennapod.core.feed.FeedItem) Playable(de.danoeh.antennapod.core.util.playback.Playable) Calendar(java.util.Calendar) MediaMetadata(com.google.android.gms.cast.MediaMetadata) WebImage(com.google.android.gms.common.images.WebImage) Feed(de.danoeh.antennapod.core.feed.Feed)

Example 2 with Playable

use of de.danoeh.antennapod.core.util.playback.Playable in project AntennaPod by AntennaPod.

the class RemotePSMP method endPlayback.

@Override
protected Future<?> endPlayback(boolean hasEnded, boolean wasSkipped, boolean shouldContinue, boolean toStoppedState) {
    Log.d(TAG, "endPlayback() called");
    boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
    if (playerStatus != PlayerStatus.INDETERMINATE) {
        setPlayerStatus(PlayerStatus.INDETERMINATE, media);
    }
    if (media != null && wasSkipped) {
        // current position only really matters when we skip
        int position = getPosition();
        if (position >= 0) {
            media.setPosition(position);
        }
    }
    final Playable currentMedia = media;
    Playable nextMedia = null;
    if (shouldContinue) {
        nextMedia = callback.getNextInQueue(currentMedia);
        boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue();
        if (playNextEpisode) {
            Log.d(TAG, "Playback of next episode will start immediately.");
        } else if (nextMedia == null) {
            Log.d(TAG, "No more episodes available to play");
        } else {
            Log.d(TAG, "Loading next episode, but not playing automatically.");
        }
        if (nextMedia != null) {
            callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode);
            // setting media to null signals to playMediaObject() that we're taking care of post-playback processing
            media = null;
            playMediaObject(nextMedia, false, true, /*TODO for now we always stream*/
            playNextEpisode, playNextEpisode);
        }
    }
    if (shouldContinue || toStoppedState) {
        boolean shouldPostProcess = true;
        if (nextMedia == null) {
            try {
                castMgr.stop();
                shouldPostProcess = false;
            } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
                Log.e(TAG, "Unable to stop playback", e);
                callback.onPlaybackEnded(null, true);
                stop();
            }
        }
        if (shouldPostProcess) {
            // Otherwise we rely on the chromecast callback to tell us the playback has stopped.
            callback.onPostPlayback(currentMedia, hasEnded, wasSkipped, nextMedia != null);
        }
    } else if (isPlaying) {
        callback.onPlaybackPause(currentMedia, currentMedia != null ? currentMedia.getPosition() : INVALID_TIME);
    }
    FutureTask<?> future = new FutureTask<>(() -> {
    }, null);
    future.run();
    return future;
}
Also used : FutureTask(java.util.concurrent.FutureTask) Playable(de.danoeh.antennapod.core.util.playback.Playable) NoConnectionException(com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException) TransientNetworkDisconnectionException(com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException) CastException(com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException)

Example 3 with Playable

use of de.danoeh.antennapod.core.util.playback.Playable in project AntennaPod by AntennaPod.

the class RemotePSMP method onRemoteMediaPlayerStatusUpdated.

private void onRemoteMediaPlayerStatusUpdated() {
    MediaStatus status = castMgr.getMediaStatus();
    if (status == null) {
        Log.d(TAG, "Received null MediaStatus");
        return;
    } else {
        Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState());
    }
    int state = status.getPlayerState();
    int oldState = remoteState;
    remoteMedia = status.getMediaInfo();
    boolean mediaChanged = !CastUtils.matches(remoteMedia, media);
    boolean stateChanged = state != oldState;
    if (!mediaChanged && !stateChanged) {
        Log.d(TAG, "Both media and state haven't changed, so nothing to do");
        return;
    }
    Playable currentMedia = mediaChanged ? localVersion(remoteMedia) : media;
    Playable oldMedia = media;
    int position = (int) status.getStreamPosition();
    // check for incompatible states
    if ((state == MediaStatus.PLAYER_STATE_PLAYING || state == MediaStatus.PLAYER_STATE_PAUSED) && currentMedia == null) {
        Log.w(TAG, "RemoteMediaPlayer returned playing or pausing state, but with no media");
        state = MediaStatus.PLAYER_STATE_UNKNOWN;
        stateChanged = oldState != MediaStatus.PLAYER_STATE_UNKNOWN;
    }
    if (stateChanged) {
        remoteState = state;
    }
    if (mediaChanged && stateChanged && oldState == MediaStatus.PLAYER_STATE_PLAYING && state != MediaStatus.PLAYER_STATE_IDLE) {
        callback.onPlaybackPause(null, INVALID_TIME);
        // We don't want setPlayerStatus to handle the onPlaybackPause callback
        setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
    }
    setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING);
    switch(state) {
        case MediaStatus.PLAYER_STATE_PLAYING:
            if (!stateChanged) {
                //These steps are necessary because they won't be performed by setPlayerStatus()
                if (position >= 0) {
                    currentMedia.setPosition(position);
                }
                currentMedia.onPlaybackStart();
            }
            setPlayerStatus(PlayerStatus.PLAYING, currentMedia, position);
            break;
        case MediaStatus.PLAYER_STATE_PAUSED:
            setPlayerStatus(PlayerStatus.PAUSED, currentMedia, position);
            break;
        case MediaStatus.PLAYER_STATE_BUFFERING:
            setPlayerStatus((mediaChanged || playerStatus == PlayerStatus.PREPARING) ? PlayerStatus.PREPARING : PlayerStatus.SEEKING, currentMedia, currentMedia != null ? currentMedia.getPosition() : INVALID_TIME);
            break;
        case MediaStatus.PLAYER_STATE_IDLE:
            int reason = status.getIdleReason();
            switch(reason) {
                case MediaStatus.IDLE_REASON_CANCELED:
                    // Essentially means stopped at the request of a user
                    callback.onPlaybackEnded(null, true);
                    setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
                    if (oldMedia != null) {
                        if (position >= 0) {
                            oldMedia.setPosition(position);
                        }
                        callback.onPostPlayback(oldMedia, false, false, false);
                    }
                    // onPlaybackEnded pretty much takes care of updating the UI
                    return;
                case MediaStatus.IDLE_REASON_INTERRUPTED:
                    // Not sure if currentMedia already reflects the to be loaded one
                    if (mediaChanged && oldState == MediaStatus.PLAYER_STATE_PLAYING) {
                        callback.onPlaybackPause(null, INVALID_TIME);
                        setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
                    }
                    setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
                    break;
                case MediaStatus.IDLE_REASON_NONE:
                    // This probably only happens when we connected but no command has been sent yet.
                    setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
                    break;
                case MediaStatus.IDLE_REASON_FINISHED:
                    // This is our onCompletionListener...
                    if (mediaChanged && currentMedia != null) {
                        media = currentMedia;
                    }
                    endPlayback(true, false, true, true);
                    return;
                case MediaStatus.IDLE_REASON_ERROR:
                    Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
                    callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_failed_media_error_skipping);
                    endPlayback(false, false, true, true);
                    return;
            }
            break;
        case MediaStatus.PLAYER_STATE_UNKNOWN:
            if (playerStatus != PlayerStatus.INDETERMINATE || media != currentMedia) {
                setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
            }
            break;
        default:
            Log.wtf(TAG, "Remote media state undetermined!");
    }
    if (mediaChanged) {
        callback.onMediaChanged(true);
        if (oldMedia != null) {
            callback.onPostPlayback(oldMedia, false, false, currentMedia != null);
        }
    }
}
Also used : Playable(de.danoeh.antennapod.core.util.playback.Playable) MediaStatus(com.google.android.gms.cast.MediaStatus)

Example 4 with Playable

use of de.danoeh.antennapod.core.util.playback.Playable in project AntennaPod by AntennaPod.

the class LocalPSMP method endPlayback.

@Override
protected Future<?> endPlayback(final boolean hasEnded, final boolean wasSkipped, final boolean shouldContinue, final boolean toStoppedState) {
    return executor.submit(() -> {
        playerLock.lock();
        releaseWifiLockIfNecessary();
        boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
        if (playerStatus != PlayerStatus.INDETERMINATE) {
            setPlayerStatus(PlayerStatus.INDETERMINATE, media);
        }
        // we're relying on the position stored in the Playable object for post-playback processing
        if (media != null) {
            int position = getPosition();
            if (position >= 0) {
                media.setPosition(position);
            }
        }
        if (mediaPlayer != null) {
            mediaPlayer.reset();
        }
        audioManager.abandonAudioFocus(audioFocusChangeListener);
        final Playable currentMedia = media;
        Playable nextMedia = null;
        if (shouldContinue) {
            // Load next episode if previous episode was in the queue and if there
            // is an episode in the queue left.
            // Start playback immediately if continuous playback is enabled
            nextMedia = callback.getNextInQueue(currentMedia);
            boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue();
            if (playNextEpisode) {
                Log.d(TAG, "Playback of next episode will start immediately.");
            } else if (nextMedia == null) {
                Log.d(TAG, "No more episodes available to play");
            } else {
                Log.d(TAG, "Loading next episode, but not playing automatically.");
            }
            if (nextMedia != null) {
                callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode);
                // setting media to null signals to playMediaObject() that we're taking care of post-playback processing
                media = null;
                playMediaObject(nextMedia, false, !nextMedia.localFileAvailable(), playNextEpisode, playNextEpisode);
            }
        }
        if (shouldContinue || toStoppedState) {
            if (nextMedia == null) {
                callback.onPlaybackEnded(null, true);
                stop();
            }
            final boolean hasNext = nextMedia != null;
            executor.submit(() -> callback.onPostPlayback(currentMedia, hasEnded, wasSkipped, hasNext));
        } else if (isPlaying) {
            callback.onPlaybackPause(currentMedia, currentMedia.getPosition());
        }
        playerLock.unlock();
    });
}
Also used : Playable(de.danoeh.antennapod.core.util.playback.Playable)

Example 5 with Playable

use of de.danoeh.antennapod.core.util.playback.Playable in project AntennaPod by AntennaPod.

the class PlaybackService method onStartCommand.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
    Log.d(TAG, "OnStartCommand called");
    final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
    final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false);
    final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
    if (keycode == -1 && playable == null && !castDisconnect) {
        Log.e(TAG, "PlaybackService was started with no arguments");
        stopSelf();
        return Service.START_REDELIVER_INTENT;
    }
    if ((flags & Service.START_FLAG_REDELIVERY) != 0) {
        Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now.");
        stopForeground(true);
    } else {
        if (keycode != -1) {
            Log.d(TAG, "Received media button event");
            handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, InputDeviceCompat.SOURCE_CLASS_NONE));
        } else if (!flavorHelper.castDisconnect(castDisconnect)) {
            started = true;
            boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true);
            boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
            boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false);
            sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
            //If the user asks to play External Media, the casting session, if on, should end.
            flavorHelper.castDisconnect(playable instanceof ExternalMedia);
            mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
        }
    }
    return Service.START_REDELIVER_INTENT;
}
Also used : Playable(de.danoeh.antennapod.core.util.playback.Playable) ExternalMedia(de.danoeh.antennapod.core.util.playback.ExternalMedia)

Aggregations

Playable (de.danoeh.antennapod.core.util.playback.Playable)40 Context (android.content.Context)15 CountDownLatch (java.util.concurrent.CountDownLatch)15 LocalPSMP (de.danoeh.antennapod.core.service.playback.LocalPSMP)11 PlaybackServiceMediaPlayer (de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer)11 AssertionFailedError (junit.framework.AssertionFailedError)11 FeedMedia (de.danoeh.antennapod.core.feed.FeedMedia)8 FeedItem (de.danoeh.antennapod.core.feed.FeedItem)7 Intent (android.content.Intent)4 PlaybackServiceTaskManager (de.danoeh.antennapod.core.service.playback.PlaybackServiceTaskManager)4 Timeline (de.danoeh.antennapod.core.util.playback.Timeline)4 SharedPreferences (android.content.SharedPreferences)3 TypedArray (android.content.res.TypedArray)3 IconDrawable (com.joanzapata.iconify.IconDrawable)3 TargetApi (android.annotation.TargetApi)2 Color (android.graphics.Color)2 PixelFormat (android.graphics.PixelFormat)2 Uri (android.net.Uri)2 Build (android.os.Build)2 Bundle (android.os.Bundle)2