در تجربه تلویزیون زنده، کاربر کانال ها را تغییر می دهد و قبل از ناپدید شدن اطلاعات به طور خلاصه اطلاعات کانال و برنامه به او ارائه می شود. سایر انواع اطلاعات، مانند پیامها ("سعی نکنید در خانه")، زیرنویسها، یا تبلیغات ممکن است نیاز به تداوم داشته باشند. مانند هر برنامه تلویزیونی، چنین اطلاعاتی نباید با محتوای برنامه در حال پخش روی صفحه تداخل ایجاد کند.
همچنین با توجه به رتبهبندی محتوا و تنظیمات کنترل والدین، و نحوه رفتار برنامهتان و اطلاعرسانی کاربر هنگام مسدود شدن یا در دسترس نبودن محتوا، در نظر بگیرید که آیا محتوای برنامه خاصی باید ارائه شود یا خیر. این درس نحوه توسعه تجربه کاربری ورودی تلویزیون خود را برای این ملاحظات توضیح می دهد.
برنامه نمونه سرویس ورودی تلویزیون را امتحان کنید.
پخش کننده را با سطح ادغام کنید
ورودی تلویزیون شما باید ویدیو را بر روی یک شی Surface
، که توسط متد TvInputService.Session.onSetSurface()
ارسال می شود، رندر کند. در اینجا مثالی از نحوه استفاده از یک نمونه MediaPlayer
برای پخش محتوا در شیء Surface
آورده شده است:
کاتلین
override fun onSetSurface(surface: Surface?): Boolean { player?.setSurface(surface) mSurface = surface return true } override fun onSetStreamVolume(volume: Float) { player?.setVolume(volume, volume) mVolume = volume }
جاوا
@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; }
به طور مشابه، در اینجا نحوه انجام آن با استفاده از ExoPlayer آمده است:
کاتلین
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 }
جاوا
@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; }
از روکش استفاده کنید
از یک پوشش برای نمایش زیرنویس ها، پیام ها، تبلیغات یا پخش داده های MHEG-5 استفاده کنید. به طور پیش فرض، پوشش غیرفعال است. میتوانید هنگام ایجاد جلسه با فراخوانی TvInputService.Session.setOverlayViewEnabled(true)
آن را فعال کنید، مانند مثال زیر:
کاتلین
override fun onCreateSession(inputId: String): Session = onCreateSessionInternal(inputId).apply { setOverlayViewEnabled(true) sessions.add(this) }
جاوا
@Override public final Session onCreateSession(String inputId) { BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); session.setOverlayViewEnabled(true); sessions.add(session); return session; }
همانطور که در اینجا نشان داده شده است از یک شی View
برای پوشش استفاده کنید که از TvInputService.Session.onCreateOverlayView()
برگردانده شده است:
کاتلین
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) } } }
جاوا
@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; }
تعریف چیدمان برای پوشش ممکن است چیزی شبیه به این باشد:
<?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>
کنترل محتوا
هنگامی که کاربر کانالی را انتخاب می کند، ورودی تلویزیون شما پاسخ تماس onTune()
در شی TvInputService.Session
انجام می دهد. با توجه به رتبهبندی محتوا، کنترلهای والدین برنامه تلویزیون سیستم تعیین میکند که چه محتوایی نمایش داده میشود. بخشهای زیر نحوه مدیریت انتخاب کانال و برنامه را با استفاده از روشهای notify
TvInputService.Session
که با برنامه تلویزیون سیستم ارتباط برقرار میکنند، شرح میدهد.
ویدیو را از دسترس خارج کنید
وقتی کاربر کانال را تغییر میدهد، میخواهید قبل از اینکه ورودی تلویزیون محتوا را ارائه کند، مطمئن شوید که صفحه نمایش هیچ گونه مصنوعات ویدیویی سرگردانی را نشان نمیدهد. هنگامی که با TvInputService.Session.onTune()
تماس می گیرید، می توانید با فراخوانی TvInputService.Session.notifyVideoUnavailable()
و عبور از ثابت VIDEO_UNAVAILABLE_REASON_TUNING
، از نمایش ویدیو جلوگیری کنید، همانطور که در مثال زیر نشان داده شده است.
کاتلین
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 }
جاوا
@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; }
سپس، هنگامی که محتوا در Surface
رندر میشود، با TvInputService.Session.notifyVideoAvailable()
تماس میگیرید تا به ویدیو اجازه نمایش داده شود، مانند:
کاتلین
fun onRenderedFirstFrame(surface:Surface) { firstFrameDrawn = true notifyVideoAvailable() }
جاوا
@Override public void onRenderedFirstFrame(Surface surface) { firstFrameDrawn = true; notifyVideoAvailable(); }
این انتقال فقط برای کسری از ثانیه طول می کشد، اما ارائه یک صفحه خالی از نظر بصری بهتر از اجازه دادن به عکس برای فلش زدن و لرزش های عجیب و غریب است.
همچنین برای اطلاعات بیشتر در مورد کار با Surface
برای رندر کردن ویدیو ، به ادغام پخش کننده با سطح مراجعه کنید.
کنترل والدین را فراهم کنید
برای تعیین اینکه آیا یک محتوای مشخص توسط کنترلهای والدین و رتبهبندی محتوا مسدود شده است، روشهای کلاس TvInputManager
، isParentalControlsEnabled()
و isRatingBlocked(android.media.tv.TvContentRating)
بررسی کنید. همچنین ممکن است بخواهید مطمئن شوید که TvContentRating
محتوا در مجموعهای از رتبهبندیهای محتوای مجاز فعلی گنجانده شده است. این ملاحظات در نمونه زیر نشان داده شده است.
کاتلین
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) }
جاوا
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); }
هنگامی که مشخص کردید آیا محتوا باید مسدود شود یا خیر، برنامه تلویزیون سیستم را با فراخوانی روش TvInputService.Session
notifyContentAllowed()
یا notifyContentBlocked()
اطلاع دهید، همانطور که در مثال قبلی نشان داده شده است.
از کلاس TvContentRating
برای تولید رشته تعریف شده توسط سیستم برای COLUMN_CONTENT_RATING
با متد TvContentRating.createRating()
استفاده کنید، همانطور که در اینجا نشان داده شده است:
کاتلین
val rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L" )
جاوا
TvContentRating rating = TvContentRating.createRating( "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
انتخاب مسیر را کنترل کنید
کلاس TvTrackInfo
اطلاعاتی در مورد آهنگ های رسانه ای مانند نوع آهنگ (ویدئو، صدا، یا زیرنویس) و غیره نگه می دارد.
اولین باری که جلسه ورودی تلویزیون شما میتواند اطلاعات آهنگ را دریافت کند، باید با TvInputService.Session.notifyTracksChanged()
با فهرستی از تمام آهنگها تماس بگیرد تا برنامه تلویزیون سیستم را بهروزرسانی کند. هنگامی که اطلاعات آهنگ تغییر کرد، دوباره با notifyTracksChanged()
تماس بگیرید تا سیستم به روز شود.
برنامه تلویزیون سیستم رابطی را برای کاربر فراهم می کند تا در صورت وجود بیش از یک آهنگ برای یک نوع آهنگ معین، یک آهنگ خاص را انتخاب کند. به عنوان مثال، زیرنویس به زبان های مختلف. ورودی تلویزیون شما با فراخوانی notifyTrackSelected()
به تماس onSelectTrack()
از برنامه تلویزیون سیستم پاسخ می دهد، همانطور که در مثال زیر نشان داده شده است. توجه داشته باشید که زمانی که null
به عنوان شناسه آهنگ ارسال می شود، این تراک را از حالت انتخاب خارج می کند .
کاتلین
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
جاوا
@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; }