Canlı TV deneyiminde kullanıcı kanal değiştirir ve bilgiler kaybolmadan önce kısa süreliğine kanal ve program bilgileri sunulur. Mesajlar veya altyazılar gibi diğer bilgi türlerinin (ör. mesajlar ("ANA EVDE TEŞVİK ETMEYİN") veya reklamlar) kalıcı olması gerekebilir. TV uygulamalarında olduğu gibi, bu tür bilgiler ekranda oynatılan program içeriğini engellememelidir.
![](https://developer.android.com/static/images/tv/do-not-attempt.png?authuser=5&hl=tr)
Şekil 1. Canlı TV uygulamasında yer paylaşımlı mesaj.
Ayrıca, içeriğin derecelendirme ve ebeveyn denetimi ayarları ve uygulamanızın içerik engellendiğinde veya kullanılamadığında nasıl davrandığı ve kullanıcıyı nasıl bilgilendirdiği göz önünde bulundurulduğunda belirli program içeriklerinin sunulması gerekip gerekmediğini de göz önünde bulundurun. Bu derste, bu hususlar için TV girişinizin kullanıcı deneyimini nasıl geliştireceğiniz açıklanmaktadır.
TV Giriş Hizmeti örnek uygulamasını deneyin.
Oynatıcıyı yüzeyle entegre edin
TV girişiniz, videoyu TvInputService.Session.onSetSurface()
yöntemi tarafından geçirilen bir Surface
nesnesinde oluşturmalıdır. Surface
nesnesindeki içeriği oynatmak için MediaPlayer
örneğinin nasıl kullanılacağına dair bir örneği burada 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 bir şekilde, ExoPlayer kullanarak bunu şu şekilde yapabilirsiniz:
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. Yer paylaşımı varsayılan olarak devre dışıdır. Aşağıdaki örnekte olduğu gibi, TvInputService.Session.setOverlayViewEnabled(true)
yöntemini çağırarak oturumu oluştururken bunu etkinleştirebilirsiniz:
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, burada gösterildiği gibi TvInputService.Session.onCreateOverlayView()
öğesinden döndürülen bir View
nesnesi kullanın:
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; }
Yer paylaşımının düzen tanımı aşağıdaki gibi görünebilir:
<?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ğırmasını işler. Sistem TV uygulamasının ebeveyn denetimleri, içerik derecelendirmesine göre hangi içeriğin gösterileceğini belirler.
Aşağıdaki bölümlerde sistem TV uygulamasıyla iletişim kuran TvInputService.Session
notify
yöntemlerini kullanarak kanal ve program seçiminin nasıl yönetileceği açıklanmaktadır.
Videoyu kullanılamaz yap
Kullanıcı kanalı değiştirdiğinde, TV girişiniz içeriği oluşturmadan önce ekranda kayıp video yapılarının görüntülenmediğinden emin olmak istersiniz. TvInputService.Session.onTune()
yöntemini çağırdığınızda aşağıdaki örnekte gösterildiği gibi TvInputService.Session.notifyVideoUnavailable()
yöntemini çağırıp VIDEO_UNAVAILABLE_REASON_TUNING
sabitini ileterek videonun sunulmasını 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; }
İçerik Surface
için oluşturulduğunda, videonun görüntülenmesine izin vermek için TvInputService.Session.notifyVideoAvailable()
yöntemini çağırırsınız. Örneğin:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Bu geçiş, saniyenin yalnızca kısa bir bölümü için sürer ancak boş ekran göstermek, resmin tuhaf yankılar ve titremelerin yanıp sönmesine izin vermekten görsel olarak daha iyidir.
Video oluşturmak için Surface
ile çalışma hakkında daha fazla bilgi için Oynatıcıyı yüzeyle entegre etme bölümüne de bakın.
Ebeveyn denetimi sağlayın
Belirli bir içeriğin ebeveyn denetimleri ve içerik derecelendirmesi tarafından engellenip engellenmediğini belirlemek için TvInputManager
sınıf yöntemlerini ve isParentalControlsEnabled()
ve isRatingBlocked(android.media.tv.TvContentRating)
öğelerini kontrol edersiniz. İçeriğin TvContentRating
değerinin şu anda izin verilen içerik derecelendirmelerine dahil edildiğinden emin olmak da isteyebilirsiniz. Dikkat edilmesi gerekenler aşağıdaki örnekte gösterilmektedir.
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ğini belirledikten sonra, önceki örnekte gösterildiği gibi TvInputService.Session
yöntemini notifyContentAllowed()
veya notifyContentBlocked()
yöntemini çağırarak sistem TV uygulamasını bilgilendirin.
Burada gösterildiği gibi, COLUMN_CONTENT_RATING
için TvContentRating.createRating()
yöntemi ile 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");
Kanal seçimini yönet
TvTrackInfo
sınıfı, medya kanallarıyla ilgili bilgileri (ör. parça türü (video, ses veya altyazı) vb.) 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 bir listesiyle birlikte TvInputService.Session.notifyTracksChanged()
çağrısını yapacaktır. Kanal bilgilerinde bir değişiklik olduğunda sistemi güncellemek için notifyTracksChanged()
numaralı telefonu tekrar arayın.
Sistem TV uygulaması, belirli bir kanal türü için birden fazla parça varsa (örneğin, farklı dillerdeki altyazılar) kullanıcının belirli bir parçayı seçebileceği bir arayüz sağlar. TV girişiniz, aşağıdaki örnekte gösterildiği gibi notifyTrackSelected()
çağrısı yaparak sistem TV uygulamasından gelen onSelectTrack()
çağrısına yanıt verir. Parça kimliği olarak null
iletildiğinde, bu işlemin kanalın seçimini kaldırdığını unutmayın.
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; }