Canlı TV deneyiminde kullanıcı kanalı değiştirir ve bilgiler kaybolmadan önce kanal ve program bilgileri kısa bir süre gösterilir. Mesajlar ("EVDE DENEMEYİN") gibi diğer bilgi türlerinin, altyazıların veya reklamların kalıcı olması gerekebilir. Bu tür bilgiler, diğer TV uygulamalarında olduğu gibi ekranda oynatılan program içeriğine müdahale etmemelidir.
Ayrıca, içeriğin derecelendirmesi ve ebeveyn denetimi ayarları göz önünde bulundurulduğunda belirli program içeriklerinin sunulup sunulmaması gerektiğini ve içerik engellendiğinde veya kullanılamadığında uygulamanızın nasıl davrandığını ve kullanıcıyı nasıl bilgilendirdiğini de göz önünde bulundurun. Bu derste, TV girişinizin kullanıcı deneyimini bu hususları göz önünde bulundurarak nasıl geliştireceğiniz açıklanmaktadır.
TV Giriş Hizmeti örnek uygulamasını deneyin.
Oynatıcıyı yüzeyle entegre etme
TV girişiniz, videoyu Surface yöntemiyle iletilen bir TvInputService.Session.onSetSurface() nesnesine işlemelidir. Surface nesnesinde içerik
oynatmak için MediaPlayer örneğinin nasıl kullanılacağına dair bir örneği aşağıda bulabilirsiniz:
Kotlin
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
Java
@Override public boolean onSetSurface(Surface surface) { if (player != null) { player.setSurface(surface); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.setVolume(volume, volume); } mVolume = volume; }
Benzer şekilde, ExoPlayer kullanarak nasıl yapacağınız aşağıda açıklanmıştır:
Kotlin
override fun onSetSurface(surface: Surface?): Boolean { player?.createMessage(videoRenderer)?.apply { type = MSG_SET_SURFACE payload = surface send() } mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.createMessage(audioRenderer)?.apply { type = MSG_SET_VOLUME payload = volume send() } mVolume = volume }
Java
@Override public boolean onSetSurface(@Nullable Surface surface) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_SURFACE) .setPayload(surface) .send(); } mSurface = surface; return true; } @Override public void onSetStreamVolume(float volume) { if (player != null) { player.createMessage(videoRenderer) .setType(MSG_SET_VOLUME) .setPayload(volume) .send(); } mVolume = volume; }
Yer paylaşımı kullanma
Altyazıları, mesajları, reklamları veya MHEG-5 veri yayınlarını görüntülemek için bir yer paylaşımı kullanın. Varsayılan olarak, yer paylaşımı devre dışıdır. Oturumu oluştururken TvInputService.Session.setOverlayViewEnabled(true) işlevini çağırarak etkinleştirebilirsiniz. Örneğin:
Kotlin
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
Java
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
Yer paylaşımı için View nesnesi kullanın. TvInputService.Session.onCreateOverlayView() tarafından döndürülen nesne, burada gösterildiği gibi olmalıdır:
Kotlin
override fun onCreateOverlayView(): View = (context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater).run { inflate(R.layout.overlayview, null).apply { subtitleView = findViewById<SubtitleView>(R.id.subtitles).apply { // Configure the subtitle view. val captionStyle: CaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) setStyle(captionStyle) setFractionalTextSize(captioningManager.fontScale) } } }
Java
@Override public View onCreateOverlayView() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.overlayview, null); subtitleView = (SubtitleView) view.findViewById(R.id.subtitles); // Configure the subtitle view. CaptionStyleCompat captionStyle; captionStyle = CaptionStyleCompat.createFromCaptionStyle( captioningManager.getUserStyle()); subtitleView.setStyle(captionStyle); subtitleView.setFractionalTextSize(captioningManager.fontScale); return view; }
Katman için düzen tanımı şöyle bir şey olabilir:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.exoplayer.text.SubtitleView android:id="@+id/subtitles" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="32dp" android:visibility="invisible"/> </FrameLayout>
İçeriği kontrol edin
Kullanıcı bir kanal seçtiğinde, TV girişiniz TvInputService.Session nesnesindeki onTune() geri çağırma işlevini işler. Sistem TV uygulamasının ebeveyn kontrolleri, içerik derecelendirmesine bağlı olarak hangi içeriğin gösterileceğini belirler.
Aşağıdaki bölümler, sistem TV uygulamasıyla iletişim kuran TvInputService.Session notify yöntemlerini kullanarak kanal ve program seçiminin nasıl yönetileceğini açıklamaktadır.
Videoyu kullanılamaz hale getir
Kullanıcı kanalı değiştirdiğinde, TV girişiniz içeriği işlemeden önce ekranda herhangi bir istenmeyen video bozukluğu görünmediğinden emin olmak istersiniz. TvInputService.Session.onTune()'ı çağırdığınızda, aşağıdaki örnekte gösterildiği gibi TvInputService.Session.notifyVideoUnavailable()'i çağırıp VIDEO_UNAVAILABLE_REASON_TUNING sabitini geçirerek videonun görüntülenmesini engelleyebilirsiniz.
Kotlin
override fun onTune(channelUri: Uri): Boolean { subtitleView?.visibility = View.INVISIBLE notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) unblockedRatingSet.clear() dbHandler.apply { removeCallbacks(playCurrentProgramRunnable) playCurrentProgramRunnable = PlayCurrentProgramRunnable(channelUri) post(playCurrentProgramRunnable) } return true }
Java
@Override public boolean onTune(Uri channelUri) { if (subtitleView != null) { subtitleView.setVisibility(View.INVISIBLE); } notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); unblockedRatingSet.clear(); dbHandler.removeCallbacks(playCurrentProgramRunnable); playCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); dbHandler.post(playCurrentProgramRunnable); return true; }
Ardından, içerik Surface'a işlendiğinde, videonun görüntülenmesini sağlamak için TvInputService.Session.notifyVideoAvailable()'i şu şekilde çağırırsınız:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Bu geçiş sadece saniyenin çok küçük bir bölümü kadar sürer, ancak boş bir ekran göstermek, görüntünün garip sinyaller ve titremeler göstermesine izin vermekten görsel olarak daha iyidir.
Ayrıca, video oluşturmak için Surface ile çalışma hakkında daha fazla bilgi için Oynatıcıyı yüzeyle entegre et bölümüne bakın.
Ebeveyn kontrolü sağlayın
Belirli bir içeriğin ebeveyn denetimi ve içerik derecelendirmesi tarafından engellenip engellenmediğini belirlemek için TvInputManager sınıf yöntemlerini, isParentalControlsEnabled() ve isRatingBlocked(android.media.tv.TvContentRating) kontrol edersiniz. Ayrıca içeriğin TvContentRating değerinin, şu anda izin verilen içerik derecelendirmeleri kümesine dahil edildiğinden emin olmak isteyebilirsiniz. Bu hususlar aşağıdaki örnekte gösterilmiştir.
Kotlin
private fun checkContentBlockNeeded() { currentContentRating?.also { rating -> if (!tvInputManager.isParentalControlsEnabled || !tvInputManager.isRatingBlocked(rating) || unblockedRatingSet.contains(rating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null) return } } lastBlockedRating = currentContentRating player?.run { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer() } notifyContentBlocked(currentContentRating) }
Java
private void checkContentBlockNeeded() { if (currentContentRating == null || !tvInputManager.isParentalControlsEnabled() || !tvInputManager.isRatingBlocked(currentContentRating) || unblockedRatingSet.contains(currentContentRating)) { // Content rating is changed so we don't need to block anymore. // Unblock content here explicitly to resume playback. unblockContent(null); return; } lastBlockedRating = currentContentRating; if (player != null) { // Children restricted content might be blocked by TV app as well, // but TIF should do its best not to show any single frame of blocked content. releasePlayer(); } notifyContentBlocked(currentContentRating); }
İçeriğin engellenip engellenmemesi gerektiğine karar verdikten sonra, önceki örnekte gösterildiği gibi TvInputService.Session yöntemini notifyContentAllowed() veya notifyContentBlocked() çağırarak sistem TV uygulamasına bildirim gönderin.
Burada gösterildiği gibi, TvContentRating.createRating() yöntemiyle COLUMN_CONTENT_RATING için sistem tanımlı dizeyi oluşturmak üzere TvContentRating sınıfını kullanın:
Kotlin
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
Java
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
Parça seçimini kontrol edin.
TvTrackInfo sınıfı, medya parçaları hakkında parça türü (video, ses veya altyazı) gibi bilgiler içerir.
TV giriş oturumunuz ilk kez parça bilgilerini alabildiğinde, sistem TV uygulamasını güncellemek için tüm parçaların listesiyle TvInputService.Session.notifyTracksChanged() işlevini çağırmalıdır. Parça bilgilerinde bir değişiklik olduğunda, sistemi güncellemek için notifyTracksChanged() işlevini tekrar çağırın.
Sistem TV uygulaması, kullanıcının belirli bir parça türü için birden fazla parça mevcutsa (örneğin, farklı dillerde altyazılar) belirli bir parçayı seçebilmesi için bir arayüz sağlar. TV girişiniz, sistem TV uygulamasından gelen onSelectTrack() çağrısına aşağıdaki örnekte gösterildiği gibi notifyTrackSelected() çağrısı yaparak yanıt verir. null parça kimliği olarak geçirildiğinde, bu parçanın seçimini kaldırır.
Kotlin
override fun onSelectTrack(type: Int, trackId: String?): Boolean = mPlayer?.let { player -> if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) return false selectedSubtitleTrackId = trackId subtitleView.visibility = if (trackId == null) View.INVISIBLE else View.VISIBLE } player.trackInfo.indexOfFirst { it.trackType == type }.let { trackIndex -> if( trackIndex >= 0) { player.selectTrack(trackIndex) notifyTrackSelected(type, trackId) true } else false } } ?: false
Java
@Override public boolean onSelectTrack(int type, String trackId) { if (player != null) { if (type == TvTrackInfo.TYPE_SUBTITLE) { if (!captionEnabled && trackId != null) { return false; } selectedSubtitleTrackId = trackId; if (trackId == null) { subtitleView.setVisibility(View.INVISIBLE); } } int trackIndex = -1; MediaPlayer.TrackInfo[] trackInfos = player.getTrackInfo(); for (int index = 0; index < trackInfos.length; index++) { MediaPlayer.TrackInfo trackInfo = trackInfos[index]; if (trackInfo.getTrackType() == type) { trackIndex = index; break; } } if (trackIndex >= 0) { player.selectTrack(trackIndex); notifyTrackSelected(type, trackId); return true; } } return false; }