Canlı TV deneyiminde, kullanıcı kanalları değiştirir ve bilgiler kaybolmadan önce kısa süreliğine kanal ve program bilgilerini alır. Diğer bilgi türleri (ör. "EVDE ATMAYIN"), altyazıların veya reklamların devam etmesi gerekebilir. Tüm TV'lerde olduğu gibi Bu tür bilgiler ekranda oynatılan program içeriğini engellememelidir.
Ayrıca göz önünde bulundurulduğunda, program içeriğinin Ayrıca, uygulamanızın ne zaman davrandığı ve bilgilendirdiği, içeriğin derecelendirmesi ve ebeveyn denetimi ayarları gibi engellenmiş veya kullanılamıyor. Bu derste, TV girişinizin kullanıcısını nasıl geliştireceğiniz açıklanmaktadır. deneyim kazanacaksınız.
Şunu deneyin: TV Giriş Hizmeti örnek uygulaması.
Oynatıcıyı yüzeyle entegre et
TV girişiniz, videoyu bir Surface
nesnesinde oluşturmalıdır ve bu nesne
TvInputService.Session.onSetSurface()
yöntemidir. Aşağıda, oynatma için MediaPlayer
örneğinin nasıl kullanılacağına dair bir örnek verilmiştir
Surface
nesnesindeki içerik:
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:
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; }
Bindirme kullan
Altyazıları, mesajları, reklamları veya MHEG-5 veri yayınlarını görüntülemek için yer paylaşımı kullanın. Varsayılan olarak
yer paylaşımı devre dışı bırakıldı. Oturumu oluştururken
TvInputService.Session.setOverlayViewEnabled(true)
,
aşağıdaki örnekte olduğu gibi:
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, aşağıda 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 denetleyin
Kullanıcı bir kanal seçtiğinde TV girişiniz TvInputService.Session
nesnesindeki onTune()
geri çağırmasını işler. Sistem TV'si
Uygulamanın ebeveyn denetimleri, içerik derecelendirmesine göre hangi içeriğin gösterileceğini belirler.
Aşağıdaki bölümlerde,
TvInputService.Session
notify
yöntemi
TV uygulamasıyla iletişim kurmalısınız.
Videoyu kullanılamaz hale getir
Kullanıcı kanalı değiştirdiğinde, ekranın başka bir kanalı izlemediğinden emin olmak
içerik oluşturmadan önceki video bozukluklarını düzeltin. TvInputService.Session.onTune()
adlı kişiyi aradığınızda,
TvInputService.Session.notifyVideoUnavailable()
numaralı telefonu arayarak videonun sunulmasını engelleyebilirsiniz
VIDEO_UNAVAILABLE_REASON_TUNING
sabitini belirtmek için
aşağıdaki örnekte gösterilmiştir.
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
için oluşturulduğunda
TvInputService.Session.notifyVideoAvailable()
ayarlamak için aşağıdaki gibi birini kullanabilirsiniz:
Kotlin
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
Java
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
Bu geçiş yalnızca saliselerce sürer ancak boş ekran göstermek görüntüde tuhaf görüntülerin ve titremelerin yanıp sönmesine izin vermekten daha iyidir.
Çalışma hakkında daha fazla bilgi için Oynatıcıyı yüzeyle entegre etme bölümüne de bakın.
Videoyu oluşturmak için Surface
ile.
Ebeveyn denetimi sağlama
Belirli bir içeriğin ebeveyn denetimleri ve içerik derecelendirmesi tarafından engellenip engellenmediğini öğrenmek için
TvInputManager
sınıf yöntemi, isParentalControlsEnabled()
ve isRatingBlocked(android.media.tv.TvContentRating)
. Siz
içeriğin TvContentRating
öğesinin
şu anda izin verilen içerik derecelendirmeleri grubudur. 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ğini belirledikten sonra, sistem TV'yi bilgilendirin
uygulamasını çağırarak
TvInputService.Session
yöntemi notifyContentAllowed()
veya
notifyContentBlocked()
olarak ayarlayın.
TvContentRating
sınıfını kullanarak şunun için sistem tanımlı dizeyi oluşturun:
COLUMN_CONTENT_RATING
ve
TvContentRating.createRating()
yöntemini kullanabilirsiniz:
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 yönetme
TvTrackInfo
sınıfı, medya kanallarıyla ilgili bilgiler içerir. Örneğin:
ses veya altyazı gibi parçalardan birini seçin.
TV giriş oturumunuz parça bilgilerini ilk kez alabildiğinde şunu çağırmalıdır:
Sistem TV uygulamasını güncellemek için tüm parçaların listesini içeren TvInputService.Session.notifyTracksChanged()
. Oradayken
takip bilgilerinde bir değişiklik,
notifyTracksChanged()
tekrar tıklayın.
Sistem TV uygulaması, birden fazla kanal varsa kullanıcının belirli bir kanalı seçmesi için bir arayüz sağlar.
Belirli bir parça türü için kullanılabilir parça; Örneğin, farklı dillerdeki altyazılar. TV'niz
giriş,
onSelectTrack()
.
telefon ederek sistem TV uygulamasından arama
notifyTrackSelected()
(aşağıdaki örnekte gösterildiği gibi) ile uyumlu olmalıdır. null
parametresi, parça kimliği olarak aktarılırsa parçanın seçimi kaldırılı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; }