Technical breakthrough docking a video player into RaceBoard

- Created MediaComponent as a reduced copy of MediaSelector
- Adding an instance of MediaComponent to the RaceBoard automatically playing the default video

TODO:
Define a concept how to deal with
- showing multiple videos (one docked, furthers as overlay)
- suppress the same video being played docked and as overlay
- which sound track to play in case of several being available
- how/where to manage/show videos in case the LeaderBoard panel is collapsed (Axel --> provide a management control on the map, show videos as overlay)
This commit is contained in:
Jens Rommel committed 2014-08-12 14:59:40 +02:00
1 parent 5bcfce5f58
commit 4eb1cd1083
9 files changed
+332 -43

No files matched your search

@@ -0,0 +1,302 @@
package com.sap.sailing.gwt.ui.client.media;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import com.google.gwt.dom.client.AudioElement;
import com.google.gwt.dom.client.MediaElement;
import com.google.gwt.media.client.Audio;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.media.MediaTrack.MediaType;
import com.sap.sailing.domain.common.media.MediaTrack.Status;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayerWithWidget;
import com.sap.sailing.gwt.ui.client.shared.components.Component;
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent;
import com.sap.sailing.gwt.ui.shared.UserDTO;
import com.sap.sse.gwt.client.player.PlayStateListener;
import com.sap.sse.gwt.client.player.TimeListener;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.gwt.client.useragent.UserAgentDetails.AgentTypes;
public class MediaComponent implements Component<Void>, PlayStateListener, TimeListener {
private VideoPlayerWithWidget activeVideoPlayer;
private final SimplePanel rootPanel = new SimplePanel();
private final Collection<MediaTrack> mediaTracks = new ArrayList<MediaTrack>();
private final RegattaAndRaceIdentifier raceIdentifier;
private final RaceTimesInfoProvider raceTimesInfoProvider;
private final Timer raceTimer;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final UserAgentDetails userAgent;
private final UserDTO user;
private final boolean autoSelectMedia;
private Date currentRaceTime;
private double currentPlaybackSpeed = 1.0d;
private PlayStates currentPlayState = PlayStates.Paused;
public MediaComponent(RegattaAndRaceIdentifier selectedRaceIdentifier, RaceTimesInfoProvider raceTimesInfoProvider,
Timer raceTimer, MediaServiceAsync mediaService, StringMessages stringMessages,
ErrorReporter errorReporter, UserAgentDetails userAgent, UserDTO user, boolean autoSelectMedia) {
this.raceIdentifier = selectedRaceIdentifier;
this.raceTimesInfoProvider = raceTimesInfoProvider;
this.raceTimer = raceTimer;
this.stringMessages = stringMessages;
this.errorReporter = errorReporter;
this.userAgent = userAgent;
this.user = user;
this.autoSelectMedia = autoSelectMedia;
}
private boolean isPotentiallyPlayable(MediaTrack mediaTrack) {
return MediaTrack.Status.REACHABLE.equals(mediaTrack.status)
|| MediaTrack.Status.UNDEFINED.equals(mediaTrack.status);
}
private void setStatus(final MediaTrack mediaTrack) {
if (!mediaTrack.isYoutube()) {
// firefox crashes in the current version when trying to read the metadata from mp4 files
if(!userAgent.getType().equals(AgentTypes.FIREFOX)) {
Audio audio = Audio.createIfSupported();
if (audio != null) {
AudioElement mediaReachableTester = audio.getAudioElement();
addLoadMetadataHandler(mediaReachableTester, mediaTrack);
mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA);
mediaReachableTester.setSrc(mediaTrack.url);
mediaReachableTester.load();
} else {
mediaTrack.status = Status.CANNOT_PLAY;
}
} else {
mediaTrack.status = Status.REACHABLE;
}
} else {
mediaTrack.status = Status.REACHABLE;
}
}
native void addLoadMetadataHandler(MediaElement mediaElement, MediaTrack mediaTrack) /*-{
var that = this;
mediaElement
.addEventListener(
'loadedmetadata',
function() {
that.@com.sap.sailing.gwt.ui.client.media.MediaSelector::loadedmetadata(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack);
});
mediaElement
.addEventListener(
'error',
function() {
that.@com.sap.sailing.gwt.ui.client.media.MediaSelector::mediaError(Lcom/sap/sailing/domain/common/media/MediaTrack;)(mediaTrack);
});
}-*/;
public void loadedmetadata(MediaTrack mediaTrack) {
mediaTrack.status = Status.REACHABLE;
}
public void mediaError(MediaTrack mediaTrack) {
mediaTrack.status = Status.NOT_REACHABLE;
}
private void playDefault() {
MediaTrack defaultVideo = getDefaultVideo();
videoChanged(defaultVideo);
}
private MediaTrack getDefaultVideo() {
for (MediaTrack mediaTrack : mediaTracks) {
if (MediaType.video.equals(mediaTrack.mimeType.mediaType) && isPotentiallyPlayable(mediaTrack)) {
return mediaTrack;
}
}
return null;
}
private void stopAll() {
clear();
}
@Override
public void playStateChanged(PlayStates playState, PlayModes playMode) {
this.currentPlayState = playState;
if (PlayModes.Replay.equals(playMode)) {
switch (this.currentPlayState) {
case Playing:
startPlaying();
break;
case Paused:
pausePlaying();
default:
break;
}
} else {
// TODO: Live mode not supported, yet.
}
}
@Override
public void playSpeedFactorChanged(double newPlaySpeedFactor) {
this.currentPlaybackSpeed = newPlaySpeedFactor;
if (activeVideoPlayer != null) {
activeVideoPlayer.setPlaybackSpeed(this.currentPlaybackSpeed);
}
}
private void pausePlaying() {
if (activeVideoPlayer != null) {
activeVideoPlayer.pauseMedia();
}
}
private void startPlaying() {
if ((activeVideoPlayer != null) && activeVideoPlayer.isCoveringCurrentRaceTime()) {
activeVideoPlayer.playMedia();
}
}
@Override
public void timeChanged(Date newRaceTime, Date oldRaceTime) {
this.currentRaceTime = newRaceTime;
if (activeVideoPlayer != null) {
activeVideoPlayer.raceTimeChanged(this.currentRaceTime);
ensurePlayState();
}
}
/**
* Wraps the callback handling functions in an object to better document their purpose.
* onSuccess and onError are simply too generic to tell about their concrete use.
* @return
*/
public AsyncCallback<Collection<MediaTrack>> getMediaLibraryCallback() {
return new AsyncCallback<Collection<MediaTrack>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Remote Procedure Call getMediaTracksForRace(...) - Failure: " + caught.getMessage());
}
@Override
public void onSuccess(Collection<MediaTrack> mediaTracks) {
MediaComponent.this.mediaTracks.clear();
MediaComponent.this.mediaTracks.addAll(mediaTracks);
for (MediaTrack mediaTrack : MediaComponent.this.mediaTracks) {
setStatus(mediaTrack);
}
if (autoSelectMedia) {
playDefault();
}
}
};
}
public void videoChanged(MediaTrack videoTrack) {
if (activeVideoPlayer != null) { // --> then reset active audio player
if (activeVideoPlayer.getMediaTrack() == videoTrack) {
return; // nothing changed
}
activeVideoPlayer = null;
rootPanel.clear();
}
if (videoTrack != null) {
if (videoTrack.isYoutube()) {
activeVideoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), false, raceTimer);
} else {
activeVideoPlayer = new VideoHtmlPlayer(videoTrack, getRaceStartTime(), false, raceTimer);
}
rootPanel.add(activeVideoPlayer.getWidget());
synchPlayState();
}
}
private long getRaceStartTime() {
return raceTimesInfoProvider.getRaceTimesInfo(raceIdentifier).startOfRace.getTime();
}
private void synchPlayState() {
if (this.currentRaceTime != null) {
activeVideoPlayer.setPlaybackSpeed(currentPlaybackSpeed);
activeVideoPlayer.raceTimeChanged(this.currentRaceTime);
ensurePlayState();
}
}
private void ensurePlayState() {
switch (this.currentPlayState) {
case Playing:
if (activeVideoPlayer.isMediaPaused() && activeVideoPlayer.isCoveringCurrentRaceTime()) {
activeVideoPlayer.playMedia();
}
break;
case Paused:
if (!activeVideoPlayer.isMediaPaused()) {
activeVideoPlayer.pauseMedia();
}
default:
break;
}
}
private void clear() {
if (activeVideoPlayer != null) {
activeVideoPlayer.shutDown();
activeVideoPlayer = null;
}
}
@Override
public String getLocalizedShortName() {
return stringMessages.videoComponentShortName();
}
@Override
public boolean hasSettings() {
return false;
}
@Override
public SettingsDialogComponent<Void> getSettingsDialogComponent() {
throw new UnsupportedOperationException();
}
@Override
public void updateSettings(Void newSettings) {
throw new UnsupportedOperationException();
}
@Override
public Widget getEntryWidget() {
return rootPanel;
}
@Override
public boolean isVisible() {
return rootPanel.isVisible();
}
@Override
public void setVisible(boolean visibility) {
rootPanel.setVisible(visibility);
}
}
@@ -39,7 +39,6 @@ import com.sap.sailing.gwt.ui.client.media.popup.YoutubeWindowPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.MediaPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.UserDTO;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.player.PlayStateListener;
@@ -62,9 +61,9 @@ public class MediaSelector implements PlayStateListener, TimeListener, MediaSele
private final RegattaAndRaceIdentifier raceIdentifier;
private final RaceTimesInfoProvider raceTimesInfoProvider;
private Timer raceTimer;
private final Timer raceTimer;
private final MediaServiceAsync mediaService;
private StringMessages stringMessages;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final UserAgentDetails userAgent;
private final UserDTO user;
@@ -320,12 +319,6 @@ public class MediaSelector implements PlayStateListener, TimeListener, MediaSele
toggleMediaButton.setVisible(isVisible);
}
// @Override
public void _raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo) {
// TODO Auto-generated method stub
}
@Override
public void audioChanged(MediaTrack audioTrack) {
if (activeAudioPlayer != null) { // --> then reset active audio player
@@ -404,12 +397,12 @@ public class MediaSelector implements PlayStateListener, TimeListener, MediaSele
if (videoTrack.isYoutube()) {
// popupPlayer = new YoutubeWindowPlayer(videoTrack, popCloseListener);
videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer, errorReporter, popupCloseListener, popoutListener);
videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer);
} else {
// popupPlayer = new VideoWindowPlayer(videoTrack, popCloseListener);
videoPlayer = new VideoHtmlPlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer, errorReporter, popupCloseListener, popoutListener);
videoPlayer = new VideoHtmlPlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer);
}
VideoFloatingContainer videoFloatingContainer = new VideoFloatingContainer(videoPlayer, videoTrack, showSynchControls, mediaService, errorReporter, popupCloseListener, popoutListener);
VideoFloatingContainer videoFloatingContainer = new VideoFloatingContainer(videoPlayer, showSynchControls, mediaService, errorReporter, popupCloseListener, popoutListener);
registerVideoContainer(videoTrack, videoFloatingContainer);
} else {
@@ -1,19 +0,0 @@
package com.sap.sailing.gwt.ui.client.media;
import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer;
public class VideoDockedContainer implements VideoContainer {
@Override
public VideoPlayer getVideoPlayer() {
// TODO Auto-generated method stub
return null;
}
@Override
public void shutDown() {
// TODO Auto-generated method stub
}
}
@@ -4,7 +4,6 @@ import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer.PopoutCloseListener;
@@ -21,7 +20,7 @@ public class VideoFloatingContainer implements VideoContainer {
private final PopoutListener popoutListener;
private final VideoSynchPlayer videoPlayer;
public VideoFloatingContainer(VideoSynchPlayer videoPlayer, final MediaTrack videoTrack, boolean showSynchControls, MediaServiceAsync mediaService, ErrorReporter errorReporter, PopoutCloseListener popCloseListener, PopoutListener popoutListener) {
public VideoFloatingContainer(VideoSynchPlayer videoPlayer, boolean showSynchControls, MediaServiceAsync mediaService, ErrorReporter errorReporter, PopoutCloseListener popCloseListener, PopoutListener popoutListener) {
this.videoPlayer = videoPlayer;
this.popoutListener = popoutListener;
@@ -42,11 +41,11 @@ public class VideoFloatingContainer implements VideoContainer {
videoPlayer.setEditFlag(mediaSynchControl);
this.dialogBox = new WindowBox(videoTrack.title, videoTrack.toString(), rootPanel, new WindowBox.PopoutHandler() {
this.dialogBox = new WindowBox(videoPlayer.getMediaTrack().title, videoPlayer.getMediaTrack().toString(), rootPanel, new WindowBox.PopoutHandler() {
@Override
public void popout() {
VideoFloatingContainer.this.popoutListener.popoutVideo(videoTrack);
VideoFloatingContainer.this.popoutListener.popoutVideo(VideoFloatingContainer.this.videoPlayer.getMediaTrack());
}
});
@@ -6,8 +6,6 @@ import com.google.gwt.media.client.MediaBase;
import com.google.gwt.media.client.Video;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer.PopoutCloseListener;
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.WithWidget;
import com.sap.sse.gwt.client.player.Timer;
@@ -18,7 +16,7 @@ public class VideoHtmlPlayer extends AbstractHtmlMediaPlayer implements VideoSyn
private final Timer raceTimer;
private EditFlag editFlag;
public VideoHtmlPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer, ErrorReporter errorReporter, PopoutCloseListener popCloseListener, PopoutListener popoutListener) {
public VideoHtmlPlayer(final MediaTrack videoTrack, long raceStartTimeMillis, boolean showSynchControls, Timer raceTimer) {
super(videoTrack);
this.raceTimer = raceTimer;
this.raceStartTimeMillis = raceStartTimeMillis;
@@ -10,8 +10,6 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer.PopoutCloseListener;
import com.sap.sailing.gwt.ui.client.media.shared.AbstractMediaPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
import com.sap.sailing.gwt.ui.client.media.shared.WithWidget;
@@ -46,7 +44,7 @@ public class VideoYoutubePlayer extends AbstractMediaPlayer implements VideoSync
private final List<DeferredAction> deferredActions = new ArrayList<DeferredAction>();
public VideoYoutubePlayer(final MediaTrack videoTrack, long raceStartTimeMillis, final boolean showControls, Timer raceTimer, ErrorReporter errorReporter, PopoutCloseListener popupCloseListener, PopoutListener popoutListener) {
public VideoYoutubePlayer(final MediaTrack videoTrack, long raceStartTimeMillis, final boolean showControls, Timer raceTimer) {
super(videoTrack);
this.raceTimer = raceTimer;
this.raceStartTimeMillis = raceStartTimeMillis;
@@ -0,0 +1,6 @@
package com.sap.sailing.gwt.ui.client.media.shared;
public interface VideoPlayerWithWidget extends VideoPlayer, WithWidget {
}
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.client.media.shared;
import com.sap.sailing.gwt.ui.client.media.MediaSynchAdapter;
public interface VideoSynchPlayer extends VideoPlayer, MediaSynchAdapter, WithWidget {
public interface VideoSynchPlayer extends VideoPlayerWithWidget, MediaSynchAdapter {
void setEditFlag(EditFlag editFlag);
@@ -38,6 +38,7 @@ import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.RegattasDisplayer;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.media.MediaComponent;
import com.sap.sailing.gwt.ui.client.media.MediaSelector;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
@@ -216,6 +217,8 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
MediaComponent mediaComponent = createMediaComponent();
components.add(mediaComponent);
components.add(windChart);
leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, components);
componentViewers.add(leaderboardAndMapViewer);
@@ -233,7 +236,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
addCompetitorsFilterControl(viewControlsPanel);
addMediaSelectorToNavigationMenu();
}
private CompetitorsFilterSets createAndAddDefaultCompetitorsFilter() {
CompetitorsFilterSets filterSets = new CompetitorsFilterSets();
@@ -266,6 +269,15 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
return filterSets;
}
private MediaComponent createMediaComponent() {
boolean autoSelectMedia = true;//getConfiguration().isAutoSelectMedia();
MediaComponent mediaComponent = new MediaComponent(selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, stringMessages, errorReporter, userAgent, this.user, autoSelectMedia);
timer.addPlayStateListener(mediaComponent);
timer.addTimeListener(mediaComponent);
mediaService.getMediaTracksForRace(selectedRaceIdentifier, mediaComponent.getMediaLibraryCallback());
return mediaComponent;
}
/**
* Adds the media selector drop down and fills it with media for the current race.
* NOTE: