Download File
Download Project
Settings
Line Wrap
Themes
default
ambiance
bespin
dracula
eclipse
material
mbo
mdn-like
neat
solarized dark
ttcn
zenburn
RichTvInputService.java
/* * Copyright 2015 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 com.example.android.sampletvinput.rich; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.media.tv.TvContentRating; import android.media.tv.TvContract; import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.media.tv.TvTrackInfo; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.accessibility.CaptioningManager; import com.example.android.sampletvinput.R; import com.example.android.sampletvinput.player.DemoPlayer; import com.example.android.sampletvinput.player.RendererBuilderFactory; import com.example.android.sampletvinput.SampleJobService; import com.google.android.exoplayer.ExoPlayer; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.text.CaptionStyleCompat; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleLayout; import com.google.android.media.tv.companionlibrary.TvPlayer; import com.google.android.media.tv.companionlibrary.model.Advertisement; import com.google.android.media.tv.companionlibrary.model.Channel; import com.google.android.media.tv.companionlibrary.model.InternalProviderData; import com.google.android.media.tv.companionlibrary.model.Program; import com.google.android.media.tv.companionlibrary.model.RecordedProgram; import com.google.android.media.tv.companionlibrary.BaseTvInputService; import com.google.android.media.tv.companionlibrary.EpgSyncJobService; import com.google.android.media.tv.companionlibrary.utils.TvContractUtils; import java.util.ArrayList; import java.util.List; /** * TvInputService which provides a full implementation of EPG, subtitles, multi-audio, parental * controls, and overlay view. */ public class RichTvInputService extends BaseTvInputService { private static final String TAG = "RichTvInputService"; private static final boolean DEBUG = false; private static final long EPG_SYNC_DELAYED_PERIOD_MS = 1000 * 2; // 2 Seconds private CaptioningManager mCaptioningManager; /** * Gets the track id of the track type and track index. * * @param trackType the type of the track e.g. TvTrackInfo.TYPE_AUDIO * @param trackIndex the index of that track within the media. e.g. 0, 1, 2... * @return the track id for the type & index combination. */ private static String getTrackId(int trackType, int trackIndex) { return trackType + "-" + trackIndex; } /** * Gets the index of the track for a given track id. * * @param trackId the track id. * @return the track index for the given id, as an integer. */ private static int getIndexFromTrackId(String trackId) { return Integer.parseInt(trackId.split("-")[1]); } @Override public void onCreate() { super.onCreate(); mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); } @Override public final Session onCreateSession(String inputId) { RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId); session.setOverlayViewEnabled(true); return super.sessionCreated(session); } @RequiresApi(api = Build.VERSION_CODES.N) @Nullable @Override public TvInputService.RecordingSession onCreateRecordingSession(String inputId) { return new RichRecordingSession(this, inputId); } class RichTvInputSessionImpl extends BaseTvInputService.Session implements DemoPlayer.Listener, DemoPlayer.CaptionListener { private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f; private static final int TEXT_UNIT_PIXELS = 0; private static final String UNKNOWN_LANGUAGE = "und"; private int mSelectedSubtitleTrackIndex; private SubtitleLayout mSubtitleView; private DemoPlayer mPlayer; private boolean mCaptionEnabled; private String mInputId; private Context mContext; RichTvInputSessionImpl(Context context, String inputId) { super(context, inputId); mCaptionEnabled = mCaptioningManager.isEnabled(); mContext = context; mInputId = inputId; } @Override public View onCreateOverlayView() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); mSubtitleView = (SubtitleLayout) inflater.inflate(R.layout.subtitleview, null); // Configure the subtitle view. CaptionStyleCompat captionStyle; float captionTextSize = getCaptionFontSize(); captionStyle = CaptionStyleCompat .createFromCaptionStyle(mCaptioningManager.getUserStyle()); captionTextSize *= mCaptioningManager.getFontScale(); mSubtitleView.setStyle(captionStyle); mSubtitleView.setFixedTextSize(TEXT_UNIT_PIXELS, captionTextSize); mSubtitleView.setVisibility(View.VISIBLE); return mSubtitleView; } private List
getAllTracks() { String trackId; List
tracks = new ArrayList<>(); int[] trackTypes = { DemoPlayer.TYPE_AUDIO, DemoPlayer.TYPE_VIDEO, DemoPlayer.TYPE_TEXT }; for (int trackType : trackTypes) { int count = mPlayer.getTrackCount(trackType); for (int i = 0; i < count; i++) { MediaFormat format = mPlayer.getTrackFormat(trackType, i); trackId = getTrackId(trackType, i); TvTrackInfo.Builder builder = new TvTrackInfo.Builder(trackType, trackId); if (trackType == DemoPlayer.TYPE_VIDEO) { if (format.maxWidth != MediaFormat.NO_VALUE) { builder.setVideoWidth(format.maxWidth); } else if (format.width != MediaFormat.NO_VALUE) { builder.setVideoWidth(format.width); } if (format.maxHeight != MediaFormat.NO_VALUE) { builder.setVideoHeight(format.maxHeight); } else if (format.height != MediaFormat.NO_VALUE) { builder.setVideoHeight(format.height); } } else if (trackType == DemoPlayer.TYPE_AUDIO) { builder.setAudioChannelCount(format.channelCount); builder.setAudioSampleRate(format.sampleRate); if (format.language != null && !UNKNOWN_LANGUAGE.equals(format.language)) { // TvInputInfo expects {@code null} for unknown language. builder.setLanguage(format.language); } } else if (trackType == DemoPlayer.TYPE_TEXT) { if (format.language != null && !UNKNOWN_LANGUAGE.equals(format.language)) { // TvInputInfo expects {@code null} for unknown language. builder.setLanguage(format.language); } } tracks.add(builder.build()); } } return tracks; } @Override public boolean onPlayProgram(Program program, long startPosMs) { if (program == null) { requestEpgSync(getCurrentChannelUri()); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); return false; } createPlayer(program.getInternalProviderData().getVideoType(), Uri.parse(program.getInternalProviderData().getVideoUrl())); if (startPosMs > 0) { mPlayer.seekTo(startPosMs); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); } mPlayer.setPlayWhenReady(true); return true; } @RequiresApi(api = Build.VERSION_CODES.N) public boolean onPlayRecordedProgram(RecordedProgram recordedProgram) { createPlayer(recordedProgram.getInternalProviderData().getVideoType(), Uri.parse(recordedProgram.getInternalProviderData().getVideoUrl())); long recordingStartTime = recordedProgram.getInternalProviderData() .getRecordedProgramStartTime(); mPlayer.seekTo(recordingStartTime - recordedProgram.getStartTimeUtcMillis()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); } mPlayer.setPlayWhenReady(true); return true; } public TvPlayer getTvPlayer() { return mPlayer; } @Override public boolean onTune(Uri channelUri) { if (DEBUG) { Log.d(TAG, "Tune to " + channelUri.toString()); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); releasePlayer(); return super.onTune(channelUri); } @Override public void onPlayAdvertisement(Advertisement advertisement) { createPlayer(TvContractUtils.SOURCE_TYPE_HTTP_PROGRESSIVE, Uri.parse(advertisement.getRequestUrl())); } private void createPlayer(int videoType, Uri videoUrl) { releasePlayer(); mPlayer = new DemoPlayer(RendererBuilderFactory.createRendererBuilder( mContext, videoType, videoUrl)); mPlayer.addListener(this); mPlayer.setCaptionListener(this); mPlayer.prepare(); } @Override public void onSetCaptionEnabled(boolean enabled) { mCaptionEnabled = enabled; if (mPlayer != null) { if (mCaptionEnabled) { mPlayer.setSelectedTrack(TvTrackInfo.TYPE_SUBTITLE, mSelectedSubtitleTrackIndex); } else { mPlayer.setSelectedTrack(TvTrackInfo.TYPE_SUBTITLE, DemoPlayer.TRACK_DISABLED); } } } @Override public boolean onSelectTrack(int type, String trackId) { if (trackId == null) { return true; } int trackIndex = getIndexFromTrackId(trackId); if (mPlayer != null) { if (type == TvTrackInfo.TYPE_SUBTITLE) { if (! mCaptionEnabled) { return false; } mSelectedSubtitleTrackIndex = trackIndex; } mPlayer.setSelectedTrack(type, trackIndex); notifyTrackSelected(type, trackId); return true; } return false; } private void releasePlayer() { if (mPlayer != null) { mPlayer.removeListener(this); mPlayer.setSurface(null); mPlayer.stop(); mPlayer.release(); mPlayer = null; } } @Override public void onRelease() { super.onRelease(); releasePlayer(); } @Override public void onBlockContent(TvContentRating rating) { super.onBlockContent(rating); releasePlayer(); } private float getCaptionFontSize() { Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); Point displaySize = new Point(); display.getSize(displaySize); return Math.max(getResources().getDimension(R.dimen.subtitle_minimum_font_size), CAPTION_LINE_HEIGHT_RATIO * Math.min(displaySize.x, displaySize.y)); } @Override public void onStateChanged(boolean playWhenReady, int playbackState) { if (mPlayer == null) { return; } if (playWhenReady && playbackState == ExoPlayer.STATE_READY) { notifyTracksChanged(getAllTracks()); String audioId = getTrackId(TvTrackInfo.TYPE_AUDIO, mPlayer.getSelectedTrack(TvTrackInfo.TYPE_AUDIO)); String videoId = getTrackId(TvTrackInfo.TYPE_VIDEO, mPlayer.getSelectedTrack(TvTrackInfo.TYPE_VIDEO)); String textId = getTrackId(TvTrackInfo.TYPE_SUBTITLE, mPlayer.getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE)); notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, audioId); notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, videoId); notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, textId); notifyVideoAvailable(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Math.abs(mPlayer.getPlaybackSpeed() - 1) < 0.1 && playWhenReady && playbackState == ExoPlayer.STATE_BUFFERING) { notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING); } } @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { // Do nothing. } @Override public void onError(Exception e) { Log.e(TAG, e.getMessage()); } @Override public void onCues(List
cues) { mSubtitleView.setCues(cues); } public void requestEpgSync(final Uri channelUri) { EpgSyncJobService.requestImmediateSync(RichTvInputService.this, mInputId, new ComponentName(RichTvInputService.this, SampleJobService.class)); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { onTune(channelUri); } }, EPG_SYNC_DELAYED_PERIOD_MS); } } @RequiresApi(api = Build.VERSION_CODES.N) private class RichRecordingSession extends BaseTvInputService.RecordingSession { private static final String TAG = "RecordingSession"; private String mInputId; private long mStartTimeMs; public RichRecordingSession(Context context, String inputId) { super(context, inputId); mInputId = inputId; } @Override public void onTune(Uri uri) { super.onTune(uri); if (DEBUG) { Log.d(TAG, "Tune recording session to " + uri); } // By default, the number of tuners for this service is one. When a channel is being // recorded, no other channel from this TvInputService will be accessible. Developers // should call notifyError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY) to alert // the framework that this recording cannot be completed. // Developers can update the tuner count in xml/richtvinputservice or programmatically // by adding it to TvInputInfo.updateTvInputInfo. notifyTuned(uri); } @Override public void onStartRecording(final Uri uri) { super.onStartRecording(uri); if (DEBUG) { Log.d(TAG, "onStartRecording"); } mStartTimeMs = System.currentTimeMillis(); } @Override public void onStopRecording(Program programToRecord) { if (DEBUG) { Log.d(TAG, "onStopRecording"); } // In this sample app, since all of the content is VOD, the video URL is stored. // If the video was live, the start and stop times should be noted using // RecordedProgram.Builder.setStartTimeUtcMillis and .setEndTimeUtcMillis. // The recordingstart time will be saved in the InternalProviderData. // Additionally, the stream should be recorded and saved as // a new file. long currentTime = System.currentTimeMillis(); InternalProviderData internalProviderData = programToRecord.getInternalProviderData(); internalProviderData.setRecordingStartTime(mStartTimeMs); RecordedProgram recordedProgram = new RecordedProgram.Builder(programToRecord) .setInputId(mInputId) .setRecordingDataUri( programToRecord.getInternalProviderData().getVideoUrl()) .setRecordingDurationMillis(currentTime - mStartTimeMs) .setInternalProviderData(internalProviderData) .build(); notifyRecordingStopped(recordedProgram); } @Override public void onStopRecordingChannel(Channel channelToRecord) { if (DEBUG) { Log.d(TAG, "onStopRecording"); } // Program sources in this sample always include program info, so execution here // indicates an error. notifyError(TvInputManager.RECORDING_ERROR_UNKNOWN); return; } @Override public void onRelease() { if (DEBUG) { Log.d(TAG, "onRelease"); } } } }