Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Exo-player two facade and forwarders #23

Merged
merged 9 commits into from
May 31, 2017

Conversation

Mecharyry
Copy link
Contributor

Problem

ExoPlayerForwarder contained all of the events that we listen to from ExoPlayer and we created Forwarders that adapted the ExoPlayer events to our no-player events. Some of these Forwarders contained state. Facade and Imple were mixing responsibilities with no clear boundary.

Solution

  • Remove the catch all Forwarder and implement specific forwarders for the events we want to listen to.
  • Create a generic forwarder that has as collaborators the specific forwarder implementations.
  • Remove the Facade from exo-player because the code is small enough to be encapsulated in one class with collaborators.

Test(s) added

No tests atm, we hope to branch off and have these covered soon.

Screenshots

No UI changes.

Paired with

@Dorvaryn and @jackSzm


import java.io.IOException;

abstract class ExoPlayerEventForwarder implements ExoPlayerForwarder {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤢 we implemented all events on this one forwarder. We now have dedicated forwarders and a generic forwarder that has the specific forwarders as collaborators.

@rock3r rock3r self-requested a review May 30, 2017 08:56
@rock3r rock3r self-assigned this May 30, 2017
@@ -13,74 +22,93 @@
import com.novoda.noplayer.VideoContainer;
import com.novoda.noplayer.VideoDuration;
import com.novoda.noplayer.VideoPosition;
import com.novoda.noplayer.exoplayer.forwarder.ExoPlayerForwarder;
import com.novoda.noplayer.player.PlayerInformation;

import java.util.Collections;
import java.util.List;

public class ExoPlayerTwoImpl extends PlayerListenersHolder implements Player {
Copy link
Contributor Author

@Mecharyry Mecharyry May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Facade became a wrapper during our refactorings so quite a few of the calls here are just inlines.


import java.io.IOException;

class BitrateForwarder implements AdaptiveMediaSourceEventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer extending from the abstract ExoPlayerEventForwarder, instead, we map directly from the ExoPlayer events to the no-player events.


@Override
public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These TODOs were hidden in the abstract ExoPlayerEventForwarder class, not anymore! We should figure out what to do with these!

import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.novoda.noplayer.listeners.BufferStateListeners;

class BufferStateForwarder implements ExoPlayer.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, not extending from the abstract ExoPlayerEventForwarder instead implementing the interface from ExoPlayer that we care about.


@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we need to figure out how we should map these from ExoPlayer to no-player


import java.util.HashMap;

class EventInfoForwarder implements ExoPlayer.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class ExoPlayerExtractorMediaSourceListener implements ExtractorMediaSource.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

import com.novoda.noplayer.listeners.PreparedListeners;
import com.novoda.noplayer.listeners.VideoSizeChangedListeners;

public class ExoPlayerForwarder {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our generic forwarder that contains all of the events that we are interested in from ExoPlayer.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class ExoPlayerVideoRendererEventListener implements VideoRendererEventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

import java.io.IOException;
import java.util.HashMap;

class ExtractorInfoForwarder implements ExtractorMediaSource.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid huge classes we have an InfoForwarder for each of the ExoPlayer interfaces that we are interested in monitoring.

import com.google.android.exoplayer2.upstream.DataSpec;
import com.novoda.noplayer.listeners.InfoListeners;

import java.io.IOException;
import java.util.HashMap;

class InfoForwarder extends ExoPlayerEventForwarder {
class MediaSourceInfoForwarder implements AdaptiveMediaSourceEventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

import com.novoda.noplayer.Player;
import com.novoda.noplayer.exoplayer.playererror.MediaSourceError;
import com.novoda.noplayer.listeners.ErrorListeners;

import java.io.IOException;

class OnErrorForwarder extends ExoPlayerEventForwarder {
class MediaSourceOnErrorForwarder implements ExtractorMediaSource.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.novoda.noplayer.listeners.CompletionListeners;

class OnCompletionForwarder implements ExoPlayer.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.


@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, we need to figure out the mapping from ExoPlayer to no-player for these additional events.

import com.novoda.noplayer.PlayerState;
import com.novoda.noplayer.listeners.PreparedListeners;

class OnPrepareForwarder implements ExoPlayer.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.


@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, again. We need to figure out the mapping between ExoPlayer and no-player for these additional events 😄

import com.novoda.noplayer.Player;
import com.novoda.noplayer.listeners.ErrorListeners;

class PlayerOnErrorForwarder implements ExoPlayer.EventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.


@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AND AGAIN! 😄 🎉 We need to figure out the mapping between exoplayer and no-player.


import java.util.HashMap;

class VideoRendererInfoForwarder implements VideoRendererEventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many info forwarders and each implements a single interface from ExoPlayer and are added to the generic forwarder.

import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.novoda.noplayer.listeners.VideoSizeChangedListeners;

class VideoSizeChangedForwarder implements VideoRendererEventListener {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as others above. We no longer extend from ExoPlayerEventForwarder but instead implement the interface we care about directly from ExoPlayer.


@Override
public void onVideoEnabled(DecoderCounters counters) {
//TODO should we send ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOOOoooo more! We need to figure out the mapping between ExoPlayer and no-player.

@@ -10,6 +10,8 @@

private final Set<Player.PreparedListener> listeners = new CopyOnWriteArraySet<>();

private boolean hasPrepared = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We moved the state up a level because it made more sense here than in each of the individual listeners.

@@ -30,7 +30,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
protected void onStart() {
super.onStart();
player = new PlayerFactory(this, PrioritisedPlayers.prioritiseMediaPlayer()).create();
player = new PlayerFactory(this, PrioritisedPlayers.prioritiseExoPlayer()).create();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂️ because we are working on ExoPlayer 👅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave a TODO maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, we should have this exposed in the UI really.

private ExoPlayerTwoImpl(SimpleExoPlayer exoPlayer, MediaSourceFactory mediaSourceFactory, ExoPlayerForwarder exoPlayerForwarder) {
this.exoPlayer = exoPlayer;
this.mediaSourceFactory = mediaSourceFactory;
forwarder = exoPlayerForwarder;
Heart.Heartbeat<Player> onHeartbeat = new Heart.Heartbeat<>(getHeartbeatCallbacks(), this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance we can move these inits to params so that we only pass in heart instead? I know this is pre-existig but we try avoiding work in ctors usually and this seems a good time to do it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have done this in a follow-up branch that we are waiting to open 😆 but yes we should try and move all of these out of the constructor.

@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
HashMap<String, String> keyValuePairs = new HashMap<>();
keyValuePairs.put("timeline", String.valueOf(timeline));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inb4 "extract constants", I am ok with one-off strings not being extracted here 👍


@Override
public void onPlayerError(ExoPlaybackException error) {
//Sent by ErrorForwarder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from the others?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. This particular event is handled by the errorForwarder and should not be handled by this Forwarder. This is because it would be a bit strange to notify the no-player complete listeners of an error, when that is handled by the no-player error listeners.

This is a small problem with using the interfaces we are interested in from ExoPlayer it leads to explicit no-ops in the specific implementations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figured that was the case, ok :)


@Override
public void onPlayerError(ExoPlaybackException error) {
//Sent by ErrorForwarder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for the onCompletionListener

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as onCompletionListener response 👅


@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
//Handled by OnPrepared and OnCompletion forwarders
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this is here then? Is it because the Exoplayer interface is having both methods and we just split them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, it is because ExoPlayer.EventListener is too broad a listener 😢 but we decided it was better than having an abstract class. Better to be explicit about these in the implementations then to hide it in the abstraction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep agreed, I'm ok with that

@@ -30,7 +30,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
protected void onStart() {
super.onStart();
player = new PlayerFactory(this, PrioritisedPlayers.prioritiseMediaPlayer()).create();
player = new PlayerFactory(this, PrioritisedPlayers.prioritiseExoPlayer()).create();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave a TODO maybe?

exoPlayer.addListener(forwarder.exoPlayerEventListener());
exoPlayer.setVideoDebugListener(forwarder.videoRendererEventListener());

exoPlayer.setPlayWhenReady(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason for this change? it'll break symmetry with the media player as the we're relying on

loadVideo -> onPrepared -> play

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we no longer need onPrepared step in exoplayer2.
But if that's the case in mediaPlayer then probably we would need to bring it back.

@ouchadam ouchadam merged commit 96e964b into exo-player-2 May 31, 2017
@Mecharyry Mecharyry deleted the refactor-exo-player-two-facade-some-more branch June 2, 2017 13:52
@Mecharyry Mecharyry mentioned this pull request Jun 5, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants