diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index ce896b8..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -RemoteDecoderTest \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 508b3d9..3452509 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -9,14 +9,10 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml index ddfada6..13b02cb 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,9 @@ - + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7c669f2..dc923cd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,4 +23,5 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.4.0' + compile project(":library-release") } diff --git a/app/src/main/java/org/mozilla/remotedecoder/EventLogger.java b/app/src/main/java/org/mozilla/remotedecoder/EventLogger.java new file mode 100644 index 0000000..c40447f --- /dev/null +++ b/app/src/main/java/org/mozilla/remotedecoder/EventLogger.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mozilla.remotedecoder; + +import android.os.SystemClock; +import android.util.Log; +import android.view.Surface; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.metadata.id3.ApicFrame; +import com.google.android.exoplayer2.metadata.id3.CommentFrame; +import com.google.android.exoplayer2.metadata.id3.GeobFrame; +import com.google.android.exoplayer2.metadata.id3.Id3Frame; +import com.google.android.exoplayer2.metadata.id3.PrivFrame; +import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; +import com.google.android.exoplayer2.metadata.id3.TxxxFrame; +import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.video.VideoRendererEventListener; +import java.io.IOException; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Logs player events using {@link Log}. + */ +/* package */ final class EventLogger implements ExoPlayer.EventListener, + AudioRendererEventListener, VideoRendererEventListener, AdaptiveMediaSourceEventListener, + ExtractorMediaSource.EventListener, StreamingDrmSessionManager.EventListener, + MetadataRenderer.Output { + + private static final String TAG = "EventLogger"; + private static final int MAX_TIMELINE_ITEM_LINES = 3; + private static final NumberFormat TIME_FORMAT; + static { + TIME_FORMAT = NumberFormat.getInstance(Locale.US); + TIME_FORMAT.setMinimumFractionDigits(2); + TIME_FORMAT.setMaximumFractionDigits(2); + TIME_FORMAT.setGroupingUsed(false); + } + + private final MappingTrackSelector trackSelector; + private final Timeline.Window window; + private final Timeline.Period period; + private final long startTimeMs; + + public EventLogger(MappingTrackSelector trackSelector) { + this.trackSelector = trackSelector; + window = new Timeline.Window(); + period = new Timeline.Period(); + startTimeMs = SystemClock.elapsedRealtime(); + } + + // ExoPlayer.EventListener + + @Override + public void onLoadingChanged(boolean isLoading) { + Log.d(TAG, "loading [" + isLoading + "]"); + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int state) { + Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " + + getStateString(state) + "]"); + } + + @Override + public void onPositionDiscontinuity() { + Log.d(TAG, "positionDiscontinuity"); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + if (timeline == null) { + return; + } + int periodCount = timeline.getPeriodCount(); + int windowCount = timeline.getWindowCount(); + Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount); + for (int i = 0; i < Math.min(periodCount, MAX_TIMELINE_ITEM_LINES); i++) { + timeline.getPeriod(i, period); + Log.d(TAG, " " + "period [" + getTimeString(period.getDurationMs()) + "]"); + } + if (periodCount > MAX_TIMELINE_ITEM_LINES) { + Log.d(TAG, " ..."); + } + for (int i = 0; i < Math.min(windowCount, MAX_TIMELINE_ITEM_LINES); i++) { + timeline.getWindow(i, window); + Log.d(TAG, " " + "window [" + getTimeString(window.getDurationMs()) + ", " + + window.isSeekable + ", " + window.isDynamic + "]"); + } + if (windowCount > MAX_TIMELINE_ITEM_LINES) { + Log.d(TAG, " ..."); + } + Log.d(TAG, "]"); + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e); + } + + @Override + public void onTracksChanged(TrackGroupArray ignored, TrackSelectionArray trackSelections) { + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo == null) { + Log.d(TAG, "Tracks []"); + return; + } + Log.d(TAG, "Tracks ["); + // Log tracks associated to renderers. + for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.length; rendererIndex++) { + TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); + TrackSelection trackSelection = trackSelections.get(rendererIndex); + if (rendererTrackGroups.length > 0) { + Log.d(TAG, " Renderer:" + rendererIndex + " ["); + for (int groupIndex = 0; groupIndex < rendererTrackGroups.length; groupIndex++) { + TrackGroup trackGroup = rendererTrackGroups.get(groupIndex); + String adaptiveSupport = getAdaptiveSupportString(trackGroup.length, + mappedTrackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)); + Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " ["); + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + String status = getTrackStatusString(trackSelection, trackGroup, trackIndex); + String formatSupport = getFormatSupportString( + mappedTrackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); + Log.d(TAG, " " + status + " Track:" + trackIndex + ", " + + getFormatString(trackGroup.getFormat(trackIndex)) + + ", supported=" + formatSupport); + } + Log.d(TAG, " ]"); + } + // Log metadata for at most one of the tracks selected for the renderer. + if (trackSelection != null) { + for (int selectionIndex = 0; selectionIndex < trackSelection.length(); selectionIndex++) { + Metadata metadata = trackSelection.getFormat(selectionIndex).metadata; + if (metadata != null) { + Log.d(TAG, " Metadata ["); + printMetadata(metadata, " "); + Log.d(TAG, " ]"); + break; + } + } + } + Log.d(TAG, " ]"); + } + } + // Log tracks not associated with a renderer. + TrackGroupArray unassociatedTrackGroups = mappedTrackInfo.getUnassociatedTrackGroups(); + if (unassociatedTrackGroups.length > 0) { + Log.d(TAG, " Renderer:None ["); + for (int groupIndex = 0; groupIndex < unassociatedTrackGroups.length; groupIndex++) { + Log.d(TAG, " Group:" + groupIndex + " ["); + TrackGroup trackGroup = unassociatedTrackGroups.get(groupIndex); + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + String status = getTrackStatusString(false); + String formatSupport = getFormatSupportString( + RendererCapabilities.FORMAT_UNSUPPORTED_TYPE); + Log.d(TAG, " " + status + " Track:" + trackIndex + ", " + + getFormatString(trackGroup.getFormat(trackIndex)) + + ", supported=" + formatSupport); + } + Log.d(TAG, " ]"); + } + Log.d(TAG, " ]"); + } + Log.d(TAG, "]"); + } + + // MetadataRenderer.Output + + @Override + public void onMetadata(Metadata metadata) { + Log.d(TAG, "onMetadata ["); + printMetadata(metadata, " "); + Log.d(TAG, "]"); + } + + // AudioRendererEventListener + + @Override + public void onAudioEnabled(DecoderCounters counters) { + Log.d(TAG, "audioEnabled [" + getSessionTimeString() + "]"); + } + + @Override + public void onAudioSessionId(int audioSessionId) { + Log.d(TAG, "audioSessionId [" + audioSessionId + "]"); + } + + @Override + public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs, + long initializationDurationMs) { + Log.d(TAG, "audioDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]"); + } + + @Override + public void onAudioInputFormatChanged(Format format) { + Log.d(TAG, "audioFormatChanged [" + getSessionTimeString() + ", " + getFormatString(format) + + "]"); + } + + @Override + public void onAudioDisabled(DecoderCounters counters) { + Log.d(TAG, "audioDisabled [" + getSessionTimeString() + "]"); + } + + @Override + public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", " + + elapsedSinceLastFeedMs + "]", null); + } + + // VideoRendererEventListener + + @Override + public void onVideoEnabled(DecoderCounters counters) { + Log.d(TAG, "videoEnabled [" + getSessionTimeString() + "]"); + } + + @Override + public void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs, + long initializationDurationMs) { + Log.d(TAG, "videoDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]"); + } + + @Override + public void onVideoInputFormatChanged(Format format) { + Log.d(TAG, "videoFormatChanged [" + getSessionTimeString() + ", " + getFormatString(format) + + "]"); + } + + @Override + public void onVideoDisabled(DecoderCounters counters) { + Log.d(TAG, "videoDisabled [" + getSessionTimeString() + "]"); + } + + @Override + public void onDroppedFrames(int count, long elapsed) { + Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]"); + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio) { + // Do nothing. + } + + @Override + public void onRenderedFirstFrame(Surface surface) { + // Do nothing. + } + + // StreamingDrmSessionManager.EventListener + + @Override + public void onDrmSessionManagerError(Exception e) { + printInternalError("drmSessionManagerError", e); + } + + @Override + public void onDrmKeysLoaded() { + Log.d(TAG, "drmKeysLoaded [" + getSessionTimeString() + "]"); + } + + // ExtractorMediaSource.EventListener + + @Override + public void onLoadError(IOException error) { + printInternalError("loadError", error); + } + + // AdaptiveMediaSourceEventListener + + @Override + public void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs) { + // Do nothing. + } + + @Override + public void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, + IOException error, boolean wasCanceled) { + printInternalError("loadError", error); + } + + @Override + public void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) { + // Do nothing. + } + + @Override + public void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded) { + // Do nothing. + } + + @Override + public void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs) { + // Do nothing. + } + + @Override + public void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, long mediaTimeMs) { + // Do nothing. + } + + // Internal methods + + private void printInternalError(String type, Exception e) { + Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); + } + + private void printMetadata(Metadata metadata, String prefix) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof TxxxFrame) { + TxxxFrame txxxFrame = (TxxxFrame) entry; + Log.d(TAG, prefix + String.format("%s: description=%s, value=%s", txxxFrame.id, + txxxFrame.description, txxxFrame.value)); + } else if (entry instanceof PrivFrame) { + PrivFrame privFrame = (PrivFrame) entry; + Log.d(TAG, prefix + String.format("%s: owner=%s", privFrame.id, privFrame.owner)); + } else if (entry instanceof GeobFrame) { + GeobFrame geobFrame = (GeobFrame) entry; + Log.d(TAG, prefix + String.format("%s: mimeType=%s, filename=%s, description=%s", + geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description)); + } else if (entry instanceof ApicFrame) { + ApicFrame apicFrame = (ApicFrame) entry; + Log.d(TAG, prefix + String.format("%s: mimeType=%s, description=%s", + apicFrame.id, apicFrame.mimeType, apicFrame.description)); + } else if (entry instanceof TextInformationFrame) { + TextInformationFrame textInformationFrame = (TextInformationFrame) entry; + Log.d(TAG, prefix + String.format("%s: description=%s", textInformationFrame.id, + textInformationFrame.description)); + } else if (entry instanceof CommentFrame) { + CommentFrame commentFrame = (CommentFrame) entry; + Log.d(TAG, prefix + String.format("%s: language=%s description=%s", commentFrame.id, + commentFrame.language, commentFrame.description)); + } else if (entry instanceof Id3Frame) { + Id3Frame id3Frame = (Id3Frame) entry; + Log.d(TAG, prefix + String.format("%s", id3Frame.id)); + } + } + } + + private String getSessionTimeString() { + return getTimeString(SystemClock.elapsedRealtime() - startTimeMs); + } + + private static String getTimeString(long timeMs) { + return timeMs == C.TIME_UNSET ? "?" : TIME_FORMAT.format((timeMs) / 1000f); + } + + private static String getStateString(int state) { + switch (state) { + case ExoPlayer.STATE_BUFFERING: + return "B"; + case ExoPlayer.STATE_ENDED: + return "E"; + case ExoPlayer.STATE_IDLE: + return "I"; + case ExoPlayer.STATE_READY: + return "R"; + default: + return "?"; + } + } + + private static String getFormatSupportString(int formatSupport) { + switch (formatSupport) { + case RendererCapabilities.FORMAT_HANDLED: + return "YES"; + case RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES: + return "NO_EXCEEDS_CAPABILITIES"; + case RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE: + return "NO_UNSUPPORTED_TYPE"; + case RendererCapabilities.FORMAT_UNSUPPORTED_TYPE: + return "NO"; + default: + return "?"; + } + } + + private static String getAdaptiveSupportString(int trackCount, int adaptiveSupport) { + if (trackCount < 2) { + return "N/A"; + } + switch (adaptiveSupport) { + case RendererCapabilities.ADAPTIVE_SEAMLESS: + return "YES"; + case RendererCapabilities.ADAPTIVE_NOT_SEAMLESS: + return "YES_NOT_SEAMLESS"; + case RendererCapabilities.ADAPTIVE_NOT_SUPPORTED: + return "NO"; + default: + return "?"; + } + } + + private static String getFormatString(Format format) { + if (format == null) { + return "null"; + } + StringBuilder builder = new StringBuilder(); + builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType); + if (format.bitrate != Format.NO_VALUE) { + builder.append(", bitrate=").append(format.bitrate); + } + if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) { + builder.append(", res=").append(format.width).append("x").append(format.height); + } + if (format.frameRate != Format.NO_VALUE) { + builder.append(", fps=").append(format.frameRate); + } + if (format.channelCount != Format.NO_VALUE) { + builder.append(", channels=").append(format.channelCount); + } + if (format.sampleRate != Format.NO_VALUE) { + builder.append(", sample_rate=").append(format.sampleRate); + } + if (format.language != null) { + builder.append(", language=").append(format.language); + } + return builder.toString(); + } + + private static String getTrackStatusString(TrackSelection selection, TrackGroup group, + int trackIndex) { + return getTrackStatusString(selection != null && selection.getTrackGroup() == group + && selection.indexOf(trackIndex) != C.INDEX_UNSET); + } + + private static String getTrackStatusString(boolean enabled) { + return enabled ? "[X]" : "[ ]"; + } + +} diff --git a/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsAudioRender.java b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsAudioRender.java new file mode 100644 index 0000000..aeda57b --- /dev/null +++ b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsAudioRender.java @@ -0,0 +1,20 @@ +package org.mozilla.remotedecoder; + +import android.util.Log; + +import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; + +/** + * Created by kilikkuo on 2/8/17. + */ + +public class GeckoHlsAudioRender extends MediaCodecAudioRenderer { + private static final String TAG = "GeckoHlsAudioRender"; + + public GeckoHlsAudioRender(MediaCodecSelector mediaCodecSelector) { + super(mediaCodecSelector); + } +} + + diff --git a/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsPlayer.java b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsPlayer.java new file mode 100644 index 0000000..5d49b12 --- /dev/null +++ b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsPlayer.java @@ -0,0 +1,182 @@ +package org.mozilla.remotedecoder; + +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.ui.SimpleExoPlayerView; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; + +import java.util.ArrayList; + +public class GeckoHlsPlayer implements ExoPlayer.EventListener { + + private static final String TAG = "GeckoHlsPlayer"; + private static final String HLS_URL = "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"; + private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); + private DataSource.Factory mediaDataSourceFactory; + private Timeline.Window window; + protected String userAgent; + private Handler mainHandler; + public final String extension = null; + public static final String EXTENSION_EXTRA = "extension"; + private EventLogger eventLogger; + + private ExoPlayer player; +// private SimpleExoPlayer player; + + private DefaultTrackSelector trackSelector; + private boolean isTimelineStatic = false; + private final Renderer[] renderers; + private MediaSource mediaSource; + + private SimpleExoPlayerView simpleExoPlayerView; + + public DataSource.Factory buildDataSourceFactory(VideoActivity va, DefaultBandwidthMeter bandwidthMeter) { + return new DefaultDataSourceFactory(va, bandwidthMeter, + buildHttpDataSourceFactory(bandwidthMeter)); + } + + public HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) { + return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter); + } + + private MediaSource buildMediaSource(Uri uri, String overrideExtension) { + int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension + : uri.getLastPathSegment()); + switch (type) { + case C.TYPE_HLS: + return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); + default: { + throw new IllegalStateException("Unsupported type: " + type); + } + } + } + + public void setSurface(Surface surface) { + player.prepare(mediaSource); + + ExoPlayer.ExoPlayerMessage[] messages = new ExoPlayer.ExoPlayerMessage[1]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayer.ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); + } + } + + if (surface != null) { + player.blockingSendMessages(messages); + } +// if (this.surface != null && this.surface != surface) { +// // If we created this surface, we are responsible for releasing it. +// if (this.ownsSurface) { +// this.surface.release(); +// } +// // We're replacing a surface. Block to ensure that it's not accessed after the method returns. +// player.blockingSendMessages(messages); +// } else { +// player.sendMessages(messages); +// } +// this.surface = surface; +// this.ownsSurface = ownsSurface; + + } + + public GeckoHlsPlayer(VideoActivity va, Intent intent) { + +// simpleExoPlayerView = (SimpleExoPlayerView) va.findViewById(R.id.player_view); +// simpleExoPlayerView.setControllerVisibilityListener(va); +// simpleExoPlayerView.requestFocus(); + + window = new Timeline.Window(); + TrackSelection.Factory videoTrackSelectionFactory = + new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); + trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); + + ArrayList renderersList = new ArrayList<>(); + renderersList.add(new GeckoHlsVideoRender(va, MediaCodecSelector.DEFAULT)); + renderersList.add(new GeckoHlsAudioRender(MediaCodecSelector.DEFAULT)); + renderers = renderersList.toArray(new Renderer[renderersList.size()]); + + player = ExoPlayerFactory.newInstance(renderers, trackSelector); +// player = ExoPlayerFactory.newSimpleInstance(va, trackSelector, new DefaultLoadControl()); + player.addListener(this); + + eventLogger = new EventLogger(trackSelector); + player.addListener(eventLogger); + + intent.setData(Uri.parse(HLS_URL)); + intent.putExtra(EXTENSION_EXTRA, extension); + + Uri[] uris = new Uri[]{intent.getData()}; + String[] extensions = new String[]{intent.getStringExtra(EXTENSION_EXTRA)}; + mainHandler = new Handler(); + userAgent = Util.getUserAgent(va, "RemoteDecoder"); + mediaDataSourceFactory = buildDataSourceFactory(va, null); + + MediaSource[] mediaSources = new MediaSource[1]; + mediaSources[0] = buildMediaSource(uris[0], extensions[0]); + mediaSource = mediaSources[0]; +// player.prepare(mediaSource); + +// simpleExoPlayerView.setPlayer(player); + + } + + @Override + public void onLoadingChanged(boolean isLoading) { + Log.d(TAG, "loading [" + isLoading + "]"); + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int state) { +// Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " +// + getStateString(state) + "]"); + if (state == ExoPlayer.STATE_READY) { + player.setPlayWhenReady(true); + } + } + + @Override + public void onPositionDiscontinuity() { + Log.d(TAG, "positionDiscontinuity"); + } + + @Override + public void onPlayerError(ExoPlaybackException e) { +// Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e); + } + + @Override + public void onTracksChanged(TrackGroupArray ignored, TrackSelectionArray trackSelections) { + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + isTimelineStatic = !timeline.isEmpty() + && !timeline.getWindow(timeline.getWindowCount() - 1, window).isDynamic; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsVideoRender.java b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsVideoRender.java new file mode 100644 index 0000000..a2aacb5 --- /dev/null +++ b/app/src/main/java/org/mozilla/remotedecoder/GeckoHlsVideoRender.java @@ -0,0 +1,21 @@ +package org.mozilla.remotedecoder; + +import android.content.Context; +import android.util.Log; + +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; + +/** + * Created by kilikkuo on 2/6/17. + */ + +public class GeckoHlsVideoRender extends MediaCodecVideoRenderer { + private static final String TAG = "GeckoHlsVideoRender"; + + public GeckoHlsVideoRender(Context context, MediaCodecSelector mediaCodecSelector) { + super(context, mediaCodecSelector); + } +} diff --git a/app/src/main/java/org/mozilla/remotedecoder/VideoActivity.java b/app/src/main/java/org/mozilla/remotedecoder/VideoActivity.java index c6ff2b7..4f32cd4 100644 --- a/app/src/main/java/org/mozilla/remotedecoder/VideoActivity.java +++ b/app/src/main/java/org/mozilla/remotedecoder/VideoActivity.java @@ -19,9 +19,15 @@ import android.view.SurfaceView; import android.view.View; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.ui.SimpleExoPlayerView; + import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.media.CodecProxy; import org.mozilla.gecko.media.Sample; +import org.mozilla.remotedecoder.GeckoHlsPlayer; import java.io.IOException; import java.nio.ByteBuffer; @@ -50,6 +56,8 @@ public class VideoActivity extends AppCompatActivity implements SurfaceHolder.Ca private CodecWorker mWorker; + private GeckoHlsPlayer geckoHlsPlayer; + class CodecWorker extends Handler { public CodecWorker(Looper looper) { super(looper); } @Override @@ -129,11 +137,14 @@ protected void onCreate(Bundle savedInstanceState) { mFrameView = findViewById(R.id.frameView); mFrameDrawable = mFrameView.getBackground(); mFrameDrawableDeath = new ColorDrawable(getResources().getColor(android.R.color.holo_orange_light)); + + geckoHlsPlayer = new GeckoHlsPlayer(this, getIntent()); } @Override public void surfaceCreated(SurfaceHolder holder) { mHolder = holder; + geckoHlsPlayer.setSurface(mHolder.getSurface()); } @Override @@ -146,7 +157,7 @@ public void surfaceChanged(SurfaceHolder holder, int format, int width, int heig mWorker.post(new Runnable() { public void run() { - startDecoding(); +// startDecoding(); } }); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d3314fe..364c2f0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,7 +15,15 @@ android:layout_height="match_parent" android:id="@+id/videoView" android:layout_alignParentTop="true" - android:layout_alignParentEnd="true" - android:layout_alignParentStart="true" - android:layout_alignParentBottom="true" /> + android:layout_alignParentStart="true" /> + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 03bced9..74b2ab0 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.2.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 122a0dc..0c7949d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Tue Feb 07 15:04:39 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/library-release/build.gradle b/library-release/build.gradle new file mode 100644 index 0000000..df49809 --- /dev/null +++ b/library-release/build.gradle @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('library-release.aar')) \ No newline at end of file diff --git a/library-release/library-release.aar b/library-release/library-release.aar new file mode 100644 index 0000000..a5db866 Binary files /dev/null and b/library-release/library-release.aar differ diff --git a/settings.gradle b/settings.gradle index e7b4def..34373c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':library-release'