مصرف رسانه بخش بزرگی از زمان صرفشده کاربر در دستگاههای همراه را تشکیل میدهد، که مستلزم آن است که تجربه بازپخش بیعیبونقص، بسیار کارآمد، و با درنظر گرفتن عمر باتری باشد. علاوهبراین، کاربران انتظار دارند این تجربهها بهطور یکپارچه در دستگاههایشان، از تلفن گرفته تا تلویزیون اتاق نشیمن و غیره، منتقل شود.
کاوش کنید که چگونه بهروزرسانیهای معماری در Media3 و ExoPlayer بازپخش یکپارچه را در همه ضرایب شکل تقویت میکنند. محور بحث بر ساختن میاناهای واکنشگرا با جعبهابزار Media3 Compose UI، حذف کردن بافرینگ در فیدها بااستفاده از PreloadManager جدید، و گسترش دادن برنامه به صفحهنمایشهای اتاق نشیمن و Android XR با CastPlayer و صدای فضایی است.
آوانویسی
بسیارخوب.
به همه خوشامد میگویم.
نام من تونی است.
من مهندس تیم رسانه Android هستم.
و همراه با کریستینا و داناوان، درباره اینکه Jetpack Media3 چگونه میتواند به شما در برآورده کردن همه نیازهای رسانهایتان در Android کمک کند صحبت خواهیم کرد.
Media3 کتابخانه پشتیبانی Android برای همه موارد مرتبط با رسانه است، چه بازپخش با ExoPlayer، همرسانی رسانه با جلسه رسانه و Cast، یا ویرایش رسانه با Transformer، یا بازرسی رسانه، مانند استخراج فریمها یا فرادادهها.
Media3 کل این فضا را در یک گروه کتابخانه واحد پوشش میدهد.
بهعنوان کتابخانه پشتیبانی، در برنامه شما گنجانده شده است.
این یعنی میتوانیم از یکپارچگی مطمئن شویم، بنابراین کد یکسانی در همه نسخههای سیستمعامل و همه دستگاهها اجرا میشود.
همچنین به قابلیت بهروزرسانی کمک میکند.
هر رفع اشکالی که انجام میدهیم به API 24 میرسد و به هیچ چرخه انتشار سیستمعاملی وابسته نیست.
و بهعنوان کتابخانه پشتیبانی، میتوانیم گزینههای سفارشیسازی بسیار غنیای را مجاز کنیم که در چارچوب Android امکانپذیر نیست.
و بهعنوان کتابخانهای در GitHub، ما در درجه اول متنباز هستیم.
این یعنی هر تغییری که ایجاد میکنیم بلافاصله در GitHub دردسترس قرار میگیرد.
ما یک ردیاب مشکل فعال داریم و درخواستهای دریافت را میپذیریم.
نکته اصلی که باید در اینجا برجسته شود، در تمام این موارد استفاده، ما واقعاً به تمام سطوح جزئیات اهمیت میدهیم، از عناصر میانای کاربر سطح بالا تا میاناهای برنامهسازی کاربردی ساده آماده استفاده، اما همچنین گزینههای سفارشیسازی عمیق، پشتیبانی گسترده از قالبها و استانداردهای مختلف، و عملکرد و کارایی که پایه و اساس همه اینها است.
Media3 کل این فضا را پوشش میدهد.
امروز میخواهیم در برخیاز این موضوعات عمیقتر شویم.
میخواهیم درباره واحد جدید Compose UI، CastPlayer جدید که همرسانی با جلسه رسانه را آسانتر میکند، خط لوله ویرایش بازسازیشده با جلوههای پیشرفته جدید، ابزارهای جدید برای بازیابی فراداده و فریمها، برخی فناوریهای جاریسازی جدید، بهویژه ویدیو و صدای Eclipsa، و تعدادی از ابزارهای کیفیت تجربه برای پیشبارگیری، پیمایش روی خط زمان، و صرفهجویی در باتری صحبت کنیم.
اکنون بیایید مستقیماً به موضوع اول بپردازیم، اینکه Media3 چگونه میتواند به ساخت عناصر میانای کاربر برای رسانه کمک کند.
اخیراً دو پیمانه جدید منتشر کردهایم: Media3 UI Compose و Media3 UI Compose Material 3.
در اینجا، میتوانید یک پخشکننده سطح بالای قابلترکیب را ببینید، همانطور که در Media3 1.10 بدون هیچ تغییر دیگری راهاندازی شده است.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}این ویژگی بهطور پیشفرض کار میکند.
فقط به پخشکننده Media3 نیاز دارد.
در این مثال، ExoPlayer است که معمولاً در مدل نمای خود دارید.
اما میتواند چیز دیگری باشد، مثل CastPlayer، کنترلکننده رسانه، یا هر پخشکننده دیگری که در برنامهتان دارید.
شایان ذکر است که ExoPlayer در اینجا منبع حقیقت است.
این شامل تمام اطلاعات است، و پخشکننده قابلترکیب مسئولیت رندر کردن رابط کاربری و اتصال همه چیز را بر عهده دارد.
اما اگر بخواهید این واسط کاربر، دکمههای جدید، موقعیتهای مختلف، سبکهای مختلف، و همپوشانیهای نوشتاری را سفارشیسازی کنید چه؟
پخشکننده قابلترکیب از واحدهای سازنده ساخته شده است.
میتوانید آنها را جایگزین کنید.
میتوانید هر بلوک را بهصورت جداگانه سفارشیسازی کنید یا میتوانید از آنها بهصورت مستقل در جای دیگری از برنامهتان استفاده کنید.
برای مثال، میتوانید بالا، مرکز، و bottomControls را بهطور جداگانه سفارشیسازی کنید.
همچنین میتوانید ظاهر شاتر را که هنگام پخش نشدن ویدیوها نشان داده میشود یا همپوشانی خطا را سفارشیسازی کنید.
اما این فقط لایه اول است.
مرکز پیشفرض و bottomControls همچنین جایگاههای دقیقتری دارند که میتوانید سفارشیسازی کنید.
برای مثال، میتوانید دکمه مرکزی را عوض کنید و بقیه موارد را همانطور که هست رها کنید، یا عناصری را در بالا یا پایین لغزنده اضافه کنید.
Media3 Compose همه واحدهای سازنده موردنیاز برای انجام این کار را ارائه میدهد.
برای مثال، چند دکمه داریم، دکمه پخش/ مکث، حالت تکرار، حالت پخش درهم، دکمه صامت کردن، و غیره.
سپس، نشانگرهای پیشرفت وجود دارد، مانند لغزنده و نوشتار موقعیت که روی صفحه نشان داده میشود.
تعدادی محفظه و همپوشانی نیز وجود دارد.
پخشکننده سطح بالای قابلترکیبی که به شما نشان دادم یک نمونه است، اما نمونههای بیشتری وجود دارد، مانند همپوشانیهای خطا یا زیرنویسها، و بسیاری موارد دیگر.
علاوهبراین، مؤلفههای مفید دیگری هم داریم، مثل این کنترلکننده کوچک در اینجا.
میتوانید آن را در برگه پایینی قرار دهید تا کاربر بتواند در برنامه شما پیمایش کند.
و هنوز کارمان تمام نشده است.
ما بهطور فعال درحال گسترش بیشتر این کتابخانه هستیم، برای مثال، با کنترلهای ویرایش جدید مانند لغزنده برش که بهزودی ارائه میشود.
اکنون بیایید این سفارشیسازی را در عمل ببینیم تا متوجه شویم این واحدهای سازنده چگونه استفاده میشوند.
Player(viewModel.exoPlayer)فرض کنید میخواهیم نوشتار موقعیت و مدت را که درحالحاضر کنار لغزنده است به بالای آن منتقل کنیم.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)بنابراین، کاری که میتوانیم انجام دهیم این است که این bottomControls را لغو کنیم.
همچنان میتوانیم از مؤلفه پیشفرض استفاده کنیم، اما شکاف چپ و راست را خالی میگذاریم تا ناپدید شوند.
و درعوض، نوشتار موقعیت و مدت را در جایگاه بالا با جداکننده سفارشی قرار میدهیم تا جالبتر بهنظر برسد.
و حالا فرض کنید میخواهیم رونهاد centerControls را کاملاً برداریم و بهجای آن، دکمه پخشی را درست بالای لغزنده با کنترل سرعت و دکمه صامت کردن قرار دهیم.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}و این دقیقاً همان ایده است.
میتوانیم centerControls را خالی کنیم تا ناپدید شود و میتوانیم جایگاه بالای لغزنده را با کمی فضای اضافی و سه دکمهای که میخواهیم نشان دهیم گسترش دهیم.
توجه داشته باشید که این دکمهها از خود کتابخانه هستند، بنابراین لازم نیست کد اضافی در آن قرار دهید.
این تمام چیزی است که نیاز دارید.
و میتوانید این مسیر را برای سبکبندی این عناصر نیز ادامه دهید.
اگر بخواهید مثلاً نماد دکمه صامت کردن را تغییر دهید اما عملکرد آن را حفظ کنید، این کار هم به همین روش انجام میشود.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)همانطور که اینجا میبینید، میتوانید یک نقاش سفارشی برای دکمه تعریف کنید و نمادها را خودتان انتخاب کنید.
این مؤلفه برخی فرادادهها را دراختیارتان قرار میدهد، بنابراین تنها کاری که باید انجام دهید این است که به وضعیت نگاه کنید، مثلاً showMuted در این مثال، تا نماد مناسب را انتخاب کنید.
And this is really the main benefit of Media3 Compose.
میتوانید روی ظاهر و حس برنامهتان تمرکز کنید و Media3 بقیه کارها را انجام میدهد.
این مؤلفه وضعیت را از پخشکننده میخواند، با پخشکننده تعامل میکند، و همه گذارها و پویانماییهای بین آنها را مدیریت میکند.
اکنون بیایید از واسط کاربر به همرسانی رسانه برویم.
میتوانید وضعیت بازپخش خود را با برنامههای دیگر در همان دستگاه یا متصل به دستگاههای ازراهدور همرسانی کنید.
روش اصلی برای انتشار بازپخش درحال انجام شما با MediaSession است.
این کار وضعیت و فراداده بازپخش شما را دردسترس دیگران قرار میدهد و به آنها اجازه میدهد بازپخش شما را کنترل کنند.
این روشی است که Gemini، اعلان رسانه واسط کاربر سیستم، Cast، Wear OS، یا برنامههای همراه Android Auto با برنامه شما ارتباط برقرار میکنند.
MediaSession یک پخشکننده Media3 را دربرمیگیرد و وقتی پخشکننده داشته باشید، ساختن آن بسیار آسان است.
MediaSession.Builder(context, player).build()این میتواند ExoPlayer یا هر پخشکننده دیگری باشد که در برنامهتان دارید، درست مثل Compose UI.
اگر در پسزمینه پخش میکنید، معمولاً این را در یک MediaSessionService یا MediaLibraryService میزبانی میکنید که بهطور خودکار تمام چرخه عمر را برای شما و چیزهایی مانند اعلان مدیریت میکند.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}اما اگر برنامه رسانهای دارید، احتمالاً ازقبل همه اینها را میدانید.
چیز جدید و هیجانانگیزی که میخواهم به شما نشان دهم CastPlayer ما است که بهتازگی بازنویسی شده است.
Google Cast به شما امکان میدهد بازپخش محلی خود را به دستگاهی از راه دور، مانند Android TV، Chromecast، یا بلندگوهای هوشمند منتقل کنید.
ما قبلاً یک Cast SDK جداگانه منتشر میکردیم که اکنون در حالت تعمیر و نگهداری است.
درعوض، Media3 Cast بهطور کامل جایگزین آن میشود زیرا ادغام بسیار سادهتر و کارآمدتری ارائه میدهد.
مهمترین عنصر در اینجا CastPlayer است که همان میانای پخشکننده استاندارد مانند ExoPlayer را پیادهسازی میکند.
درحالیکه ExoPlayer برای بازپخش محلی است، CastPlayer یک نمونه واگذارکننده است که میتواند بهصورت محلی ازطریق ExoPlayer یا از راه دور ازطریق پروتکل Cast پخش کند.
و همه کارها را انجام میدهد.
این چارچوب به تغییرات دستگاه خروجی گوش میدهد، اتصال را برقرار میکند، وضعیت را همگامسازی میکند، و درصورت نیاز، بازپخش را برای شما منتقل میکند.
به همین دلیل، ادغام CastPlayer در برنامهتان بسیار ساده است.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()بهجای ایجاد ExoPlayer و همرسانی آن با MediaSession به این صورت، از CastPlayer استفاده میکنید و تمام.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()تنها کار دیگری که باید انجام دهید این است که سیستم Cast را قبلاز استفاده در جایی در برنامهتان مقداردهی اولیه کنید.
توجه داشته باشید که این CastPlayer یک ExoPlayer را بهصورت داخلی برای بازپخش محلی میسازد، بنابراین برای سفارشیسازی بیشتر پخشکننده محلی، میتوانید ExoPlayer خودتان را نیز بسازید و آن را بهعنوان پخشکننده محلی به این CastPlayer ارسال کنید.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()و این دوباره همان اصل Media3 است.
ما بلوکهای ساختمانی ساده و آماده استفاده API را ارائه میدهیم، اما همه گزینههای سفارشیسازی را نیز که برای سفارشیسازی بیشتر نیاز دارید دراختیارتان میگذاریم.
و ممکن است برایتان سؤال باشد که کاربر چگونه در این راهاندازی بین بازپخش محلی و ازراهدور جابهجا میشود.
این ویژگی ازقبل با کادر گفتگوی تعویضکننده خروجی سیستم در اعلان رسانه بهطور پیشفرض کار میکند.
دکمه کوچکی است که در گوشه قرار دارد.
اما اگر بخواهید دکمه پخش محتوا را در صفحه بازپخش نشان دهید تا بیشتر قابلکشف باشد چه؟
این «رابط کاربری Compose» سفارشیشده را که چند دقیقه پیش ساختیم بهخاطر دارید؟
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast همچنین یک دکمه آماده ارائه میدهد که میتوانید آن را در آنجا قرار دهید.
به همین سادگی است که مثلاً MediaRouteButton آماده را به topControls اضافه کنید و برای راهاندازی به پارامترهای بیشتری نیاز نداریم، واقعاً همین است.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)و با کلیک کردن روی این دکمه، همان کادر گفتگوی تعویضکننده خروجی سیستم باز میشود و سپس CastPlayer بازپخش را برای شما منتقل میکند.
و همچنین توجه داشته باشید که من CastPlayer را به میانای کاربر در اینجا متصل کردم تا مطمئن شوم هر اتفاقی که از راه دور میافتد در میانای کاربر محلی شما منعکس میشود.
و با این مقدمه، رشته کلام را به کریستینا میسپارم تا درباره موارد استفاده ویرایش صحبت کند.
متشکریم.
سلام، حالا بیایید به ویرایش و جلوههای پیشرفته بپردازیم.
میاناهای برنامهسازی کاربردی Media3 از چندین مؤلفه تشکیل شدهاند.
اول اینکه Transformer نقطه ورودی اصلی برای میاناهای برنامهسازی کاربردی ویرایش ما است.
Transformer رسانه را پردازش، تراتبدیل، و به فایل صادر میکند.
مورد بعدی CompositionPlayer است که پیشنمایشهای همزمان از ترکیببندیهای رسانهای شما را ارائه میدهد.
این ویژگی به شما امکان میدهد ویرایشهایتان را قبلاز صادر کردن نهایی پیشدید و تنظیم دقیق کنید.
سپس جلوهها را داریم.
این ویژگی به شما کنترل کامل بر پردازش فریمبهفریم را میدهد، چه درحال اعمال جلوههای بصری ساده باشید و چه درحال افزودن رونهادهای گرافیکی.
درنهایت، Muxer را داریم.
این سرویس بهعنوان مرحله خروجی نهایی در خط لوله ما عمل میکند، قطعههای صوتی، تصویری، و فراداده پردازششده را میگیرد و آنها را در محفظههای استاندارد MP4 مینویسد.
ما سال گذشته را صرف بازنگری و بازسازی خط لوله ویرایش ویدیو خود از پایه کردیم و امروز، خوشحالم که بهبودهای عمده را به اشتراک بگذارم.
چرا خط لوله را بازسازی کردیم؟
بنابراین معماری قدیمی چالشهای متعددی را ایجاد میکرد.
اول اینکه نمیتوانست فراداده را در همه مراحل پردازش منتشر کند.
این ابزار بهشدت با OpenGL مرتبط بود که انعطافپذیری را برای ابزارهای گرافیکی مدرن محدود میکرد.
و میانای برنامهسازی کاربردی پیچیده آن، سفارشیسازی و نگهداری را به مانعی بزرگ برای توسعهدهندگان تبدیل کرده بود.
برای رسیدگی به این چالشها، خط لوله پردازش کاملاً جدیدی ساختیم.
اول اینکه، مونتاژ کردن ترکیبهای چنددارایی پیچیده اکنون بسیار سادهتر است.
دوم اینکه با جدا شدن از OpenGL، آزادی کامل دارید تا از چارچوبهای گرافیکی مدرن، مانند WebGPU، استفاده کنید.
و درنهایت، Media3 اکنون رمزگشا و رمزگذار را مستقیماً مدیریت میکند و از ثبات و کارایی بیشتر در همه دستگاهها اطمینان حاصل میکند.
بیایید نگاهی به نحوه عملکرد این خط لوله بیندازیم.
رمزگشاها فریمها را مستقیماً به میانگیرهای سختافزاری مدیریتشده توسط Media3 ارسال میکنند.
«انبوهشگر قاب» قابها را در چندین توالی هماهنگ میکند و به پردازشگر قاب سفارشی شما دسترسی مستقیم میدهد تا آن میانگیرها را قبلاز ارسال به کدبند بخواند و پردازش کند.
میانای FrameProcessor بهعنوان نقطه افزونه اصلی برای پردازش در سطح برنامه عمل میکند.
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}ازطریق میانای شنونده، برنامه شما هر زمان که فریمی پردازش میشود به Media3 اطلاع میدهد و حصارهای همگامسازی را ارسال میکند تا Media3 دقیقاً بداند چه زمانی میتوان از میانگیرها بهطور ایمن دوباره استفاده کرد.
نکتهای درباره حافظه یکپارچه: با واگذاری مالکیت میانگیرهای سختافزاری به Media3، برنامه شما میتواند دادهها را مستقیماً در محل بخواند و اصلاح کند.
این رویکرد بدون کپی، سربار حافظه را کاهش میدهد و پردازش را بسیار سریع نگه میدارد.
یکی از توسعهدهندگانی که از میاناهای برنامهسازی کاربردی ویرایش جدید بهره میبرد Adobe است.
تیم Adobe Premiere از میاناهای برنامهسازی کاربردی ویرایش Media3 بهعنوان پایه زیرساخت ویرایش رسانه خود استفاده کرد تا از سازگاری بیشتر در سراسر بومسازگان Android اطمینان حاصل کند، درحالیکه انعطافپذیری لازم برای استفاده از خط لوله سفارشی خود و WebGPU را حفظ کرد.
سپس، با خوشحالی اعلام میکنم که جلوههای جدیدی را بهعنوان بخشی از نسخه Media3 1.12 منتشر میکنیم.
شروع با بهبودهای ویدیو.
در پشت صحنه، معماری ما بر راهاندازی پیمانهای متکی است.
برنامه شما با «خدمات Play» جلوه Media3 ادغام میشود که با ماژول «بهبود خدمات Play» مرتبط است.
واحد «بهبود ویدیو» شامل قابلیتهای نگاشت رنگ، کاهش نوفه، رفع محوشدگی، و اَبَروضوح است.
ادغام کردن Video Enhance در خط لوله شما فقط به چند خط کد نیاز دارد.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)بااستفاده از EnhancementSessionDecorator.Builder، میتوانید قابلیتهایی مثل نگاشت رنگ، رفع تاری، یا ارتقای مقیاس را بهطور انتخابی فعال کنید و frameProcessorFactory حاصل و کمکافزارهای میانگیر سختافزار را به Transformer.Builder منتقل کنید.
جلوه هیجانانگیز دیگری که در Media3 1.12 ارائه میشود «صدای استودیو» است.
«صدای استودیویی» بهطور خودکار قطعه صوتی ویدیو شما را تجزیهوتحلیل میکند و آن را به بخشهای مجزا مانند گفتار، نوفه محیط، و موسیقی تقسیم میکند.
محتواسازان میتوانند بهراحتی حواسپرتیهای ناشی از باد یا جمعیت را کاهش دهند و درعینحال گفتگو و موسیقی واضح را در اولویت قرار دهند.
برای ادغام «صدای استودیویی»، پردازنده صوتی «صدای استودیویی» را در داخل شیء جلوه ارسال کنید.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()این کار پردازنده صوتی را به فایل رسانهای شما پیوست میکند.
هنگام ایجاد پردازنده صوتی «صدای استودیو»، فراخوان شنونده را پیوست کنید تا نگاشت کانال صوتی تجزیهوتحلیلشده را ازطریق onResult دریافت کنید.
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)وقتی نتیجه عملیات دردسترس قرار گرفت، هر کانال را تکرار کنید تا نمایه کانال، نوع صدای شناساییشده، و برچسب خوانا را استخراج کنید.
پس بیایید «صدای استودیویی» را در عمل بشنویم.
بنابراین، اولین جلوه. عزیزم، لطفاً گریه نکن.
داریم برای شامتان برش میزنیم.
میتوانید صدای گریه کودک را در پسزمینه بشنوید، و اکنون جلوه اعمال شده است.
بله، همانطور که میشنوید، بهلطف جلوه «صدای استودیو»، صدای گریه نوزاد در پسزمینه شنیده نمیشود.
اکنون بیایید به بازیابی فراداده بپردازیم.
بررسی خواهیم کرد که Media3 چگونه خواندن فرادادههای محفظه و استخراج فریمها را برای شما آسان میکند.
با شروع از MetadataRetriever، این کتابخانه بهطور یکپارچه با روالهای همزمان Kotlin ادغام میشود تا کدی تمیز و غیرمسدودکننده ارائه دهد.
این API طراحی تمیزتر و با خطای کمتری دارد و از یک بازیاب تغییرناپذیر برای هر فایل رسانهای استفاده میکند.
این ویژگی بهطور مداوم در همه نسخههای میانای برنامهسازی کاربردی و دستگاهها کار میکند.
و از طیف گستردهای از قالبهای رسانهای بهطور پیشفرض پشتیبانی میکند.
در اینجا مثالی کاربردی بااستفاده از MetadataRetriever آورده شده است.
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}بااستفاده از روالهای Kotlin، میتوانید بهصورت ناهمگام مدتزمان را بازیابی کنید، قالبهای قطعه و فراداده را بررسی کنید، همه در یک بلوک تمیز.
اکنون بیایید درباره FrameExtractor صحبت کنیم.
این کار باعث میشود استخراج فریمهای ویدیو رمزگشاییشده از فایل رسانهای سریع و کارآمد باشد.
این ابزار منابع زیربنایی مانند رمزگشاها را بهطور خودکار مدیریت میکند، به شما امکان میدهد جلوههای دیداری یا نگاشت رنگ HDR را اعمال کنید، و کنترل جستجوی دقیقی را دراختیارتان قرار میدهد تا بتوانید دقیقاً به قاب موردنظرتان دسترسی پیدا کنید.
در اینجا نحوه استخراج قاب در کد نشان داده شده است.
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}شما یک نمونه را با گزینههایی مانند پارامترهای جستجو، کاهش مقیاس GPU، جلوهها و پشتیبانی HDR میسازید، سپس بهصورت ناهمگام بااستفاده از فراخوانیهای تعلیق تمیز، منتظر تصاویر کوچک و فریمهای خاص میمانید.
و حالا، به Donovan.
متشکرم، کریستینا.
اکنون میخواهم درباره برخیاز پشتیبانیهای فناوری جاریسازی زیربنایی که در Media3 اضافه کردهایم صحبت کنم، ازجمله پشتیبانی از قالبهایی مانند Eclipsa Video و Eclipsa Audio.
بنابراین، تلاش اصلی در Media3 این است که از استانداردهای جاریسازی جلوتر بماند تا هر بار که مشخصات جدیدی ارائه میشود، مجبور نباشید خط لوله رسانه خود را بازنویسی کنید.
در اینجا چند نمونه از چیزهایی که اخیراً عرضه کردهایم آورده شده است.
ما هدایت محتوا داریم که امکان تعویض پویای مسیرهای CDN پسین را در میان جاریسازی فراهم میکند.
ما از درج آگهی با راهنمایی سرور برای اتصال یکپارچه آگهی شخصیسازیشده پشتیبانی میکنیم.
همچنین پشتیبانی از AV1 را در چندین نقطه ورودی، ازجمله پشتیبانی کامل از AV1 HDR، معرفی کردهایم.
ما از ویدیو حرفهای Android برای ضبط و ویرایش با کیفیت بالا پشتیبانی میکنیم.
همچنین درحال افزودن پشتیبانی از عکسهای حرکتی در هر دو قالب JPEG و HEIC هستیم.
و البته، درحال افزودن پشتیبانی از Eclipsa Video و Eclipsa Audio هستیم که بهزودی بیشتر درباره آنها صحبت خواهم کرد.
خبر خوب این است که با عرضه پشتیبانی دستگاه، همه این موارد بهطور خودکار پشتیبانی میشود.
بنابراین، اکنون بیایید نگاهی به تکامل بعدی HDR، یعنی Eclipsa Video، و مشکل اصلی که سعی در حل آن دارد بیندازیم.
بنابراین، قالبهای HDR قبلی فرادادههای توصیفی ارائه میکردند، بنابراین تعیین میکردند که صحنه چگونه باید بهنظر برسد، اما واقعاً به نمایشگر نمیگفتند که چگونه آن را پرداز کند.
پس بیایید ابتدا این را زمینهسازی کنیم.
برای محیطهای مختلف، توصیههای متفاوتی برای میانگین روشنایی صفحهنمایش وجود دارد تا بتوانید محتوایتان را بهطور واضح و راحت مشاهده کنید.
بنابراین در اتاقی کمنور، این محدوده بین ۱۰۰ تا ۳۰۰ نیت خواهد بود.
و سپس، البته، وقتی وارد محیطهای روشنتر و روشنتر میشوید، صفحهنمایش باید با محیط هماهنگ شود و روشنتر شود تا بتوانید محتوایتان را بهوضوح ببینید، تا جایی که وقتی در نور مستقیم خورشید به تلفن نگاه میکنید، ممکن است صفحهنمایش نیاز داشته باشد به ۱۰۰۰، ۲۰۰۰، یا حتی نیتهای بیشتری برسد تا بتوانید محتوایتان را مشاهده کنید.
SDR سنتی در محدوده ۱۰۰ تا ۳۰۰ کار میکند.
و البته HDR برای روشن کردن سراسری صفحهنمایش نیست.
میخواهید دامنه پویا را گسترش دهید تا برای چیزهایی مثل آتش و نور خورشید، روشنشدگیهای محلی داشته باشید.
اما تلفن هوشمند چند پیچیدگی را به این ترکیب اضافه میکند، چون ما این دستگاهها را همهجا با خود میبریم، به اتاق تاریک خانهمان یا به ساحل روشن.
این موضوع در ترکیب با این واقعیت که بسیاری از برنامهها عناصر میانای کاربر SDR را با محتوای SDR و محتوای HDR در فیدهای ترکیبی ترکیب میکنند، میتواند منجر به آن اثر آزاردهنده و کورکننده شود که گاهی اوقات ممکن است تجربه کنید، بهویژه اگر اواخر شب درحال پیمایش باشید.
و بنابراین میتوانیم ببینیم چرا HDR ارزشش را دارد، درست است؟
این کار باعث میشود محتوای ما در محیطهای بیشتری عالی بهنظر برسد.
و حتی شاهد افزایش تعامل کاربر هنگام روشن بودن HDR بودهایم.
اما مشکل این است که قالبهای HDR موجود این محیطها را درنظر نمیگیرند.
اینجا است که Eclipsa Video واقعاً میدرخشد.
مکث برای خنده.
متشکریم.
متشکریم.
بنابراین، Eclipsa Video بهجای فراداده توصیفی، فراداده تجویزی ارائه میدهد.
و این واقعاً بر سه ستون اصلی بنا شده است.
ستون اول یک لنگر سفید مرجع است.
بنابراین، این کار روشنایی استاندارد را روی درجه سفیدی SDR نمایشگر که با محیط تنظیم شده است قفل میکند و این امر از افزایش ناگهانی روشنایی جلوگیری میکند.
رکن دوم، خط مبنای SDR محتواساز است.
این کار باعث میشود منحنی نگاشت رنگمایه محتواساز دقیقاً به SDR برگردد.
سوم، منحنیهای بهره تطبیقی فضای آزاد را داریم که بهطور قطعی نورپردازیها را مقیاسبندی میکنند تا با فضای آزاد HDR همزمان نمایشگر مطابقت داشته باشند، بهطوریکه محتوا با محیطی که در آن مشاهده میشود مطابقت داشته باشد.
و این ویژگی در ExoPlayer بهصورت پیشفرض در Media3 1.11 در دستگاههای دارای Android 17 و بالاتر دردسترس است.
و Eclipsa Video براساس استانداردهای باز برای سازگاری بینصنعتی ساخته شده است.
بنابراین هیچوقت زمانی بهتر از حالا برای پشتیبانی از HDR نبوده است.
در بخش صدا، Eclipsa Audio را داریم که یک قالب صدای فضایی سهبعدی متنباز و بدون بهره مالکانه است.
این ویژگی براساس مشخصات IAMF «اتحاد برای رسانه باز» است.
این قالب، Ambisonics مبتنی بر صحنه را با صدای مبتنی بر کانال در یک جاریسازی واحد با چندین ارائه ترکیبی ترکیب میکند.
بنابراین این امکان دستکاریهای شیءمانند را برای چیزهایی مثل بهبود گفتگو فراهم میکند و روی همه چیز از هدفونهای استریو گرفته تا تنظیمات صدای فراگیر کار خواهد کرد.
درواقع دو مسیر بازپخش در Media3 وجود دارد.
برای Android 17 و نسخههای بالاتر، ExoPlayer و Android Audio Framework بهطور خودکار Eclipsa Audio را پرداز خواهند کرد.
و برای Android 16 و نسخههای قبلی، هنوز گزینهای دارید.
میتوانید افزونه IAMF را ازطریق کتابخانه برافزای اختیاری Media3 فعال کنید.
و در بخش ساخت، میتوانید بااستفاده از ترکیبکنندههای Media3، قطعههای صوتی IAMF بنویسید.
اما پشتیبانی از قالبهای با کیفیت بالا تنها نیمی از داستان است.
ما باید آن کیفیت را بهطور مؤثر ارائه دهیم.
بنابراین اکنون میخواهم درباره اینکه Media3 چگونه خطوط لوله ما را سریعتر و کارآمدتر میکند صحبت کنم.
مانند بسیاری از چیزهایی که امروز درباره آنها صحبت کردیم، اکثر این بهبودهای عملکرد بهسادگی با بهروز نگه داشتن نسخه Media3 شما دردسترس هستند.
یکی از ویژگیهایی که داریم انتقال بار صوتی PCM است که میتواند بازپخش صوتی را به واحد پردازش کممصرف هدایت کند.
در تحلیل اولیه، شاهد کاهش مصرف باتری تا ۲۰٪ برای پخش صدا با صفحهنمایش خاموش بودهایم.
علاوهبر بارگذاری صدای PCM، زمانبندی پویایی داریم که بیدار شدن حلقه رندر داخلی ExoPlayer را با VSYNC و دردسترس بودن میانگیر رمزگشا همراستا میکند.
همچنین پیشگرمایش پردازنده گرافیکی داریم که به شما امکان میدهد پردازنده گرافیکی ویدیو ثانویه را پیشگرم کنید، که این کار باعث انتقال فوری فهرست پخش و پیوند آگهی آسانتر میشود.
همچنین، تشخیص گیر کردن پخشکننده و پیشذخیرهسازی داریم که میانگیرها و پردازندههای گرافیکی متوقفشده را بهطور خودکار تشخیص میدهد و حافظه پیشذخیرهشده را بهطور تمیز محدود میکند.
حالت بهینه دیگری که داریم حالت پیمایش روی خط زمان است.
و این برای برنامههایی است که کاربران شما مکرراً در محتوا پیمایش روی خط زمان انجام میدهند.
بنابراین وقتی حالت پیمایش روی خط زمان را فعال میکنید، کشیدن نوار جستجو بسیار پاسخگوتر و روانتر میشود، همانطور که در اینجا میبینید.
البته این درحالی است که حالت پیمایش روی خط زمان خاموش است.
و در سمت چپ، میبینیم که حالت پیمایش روی خط زمان روشن است.
متشکریم.
متشکریم.
بسیار خوب.
اکنون بیایید درباره «سلامت سیستم Android» صحبت کنیم.
میدانید، ما باید به منابع مشترک RAM، CPU، و باتری احترام بگذاریم، بنابراین وقتی Android قوانین خود را درباره «تقویت صدا» و سرویسهای پیشزمینهای سختتر میکند، برنامههایی که سرویسهای خام و نگاشتهای بیتی خود را مدیریت میکنند اغلب ممکن است با خطاهایی مواجه شوند.
برخیاز مواردی که ممکن است دیده باشید عبارتاند از استثناهای غیرمجاز شروع سرویس پیشزمینهای یا استثناهای کمبود حافظه Binder.
بنابراین، جلسه سرویس رسانه و مدیر اعلان رسانه و همچنین خطوط لوله نگاشت بیتی خود را از چند جهت تقویت کردهایم، ازجمله اینکه از هدفهای سرویس پیشزمینهای قدیمی جلوگیری میکنیم و حافظه آثار هنری و موارد مشابه را محدود میکنیم.
اما نکته مهم برای شما این است که اگر از Media3 استفاده میکنید و راهنمای استاندارد را دنبال میکنید، همه این محدودیتهای سلامت سیستم بدون هیچ تأثیری برای شما مدیریت میشوند.
و درنهایت، میخواهم درباره ویژگیای صحبت کنم که بهزودی به Media3 اضافه میشود.
وقتی زمان کدبندی یا صادر کردن ویدیو میرسد، تنوع سختافزار میتواند یکی از بزرگترین چالشها باشد.
بسیاریاز برنامهها از پیکربندی کدک یکسان برای همه استفاده میکنند که همانطور که میدانید، منجر به کیفیت و عملکرد غیربهینه میشود.
به همین دلیل CodecDB را ساختیم.
CodecDB براساس اندازهگیریهای گسترده ویدیوهای واقعی بااستفاده از سنجههای کیفیت استانداردشده و محکزنی سختافزار در دستگاههای Android ساخته شده است.
پس بیایید آن را در عمل ببینیم.
در سمت راست، قابی از ویدیویی داریم که با تلفن گرفتهایم و در آن فقط از پیکربندی ویدیویی بسیار عمومی استفاده کردهایم که ممکن است در بسیاری از دستگاهها استفاده کنیم.
و در سمت چپ، از پیکربندی بهینه در سطح دستگاه استفاده کردیم که از CodecDB دریافت کردیم.
پس تفاوتها کجا هستند؟
اگر به مناطق با جزئیات بالا نگاه کنید، بیشترین تفاوت کیفیت را خواهید دید.
بنابراین به وضوح شاخههای درختان و بوتهها یا وضوح لبه ساختمان نگاه کنید.
بنابراین آنچه در اینجا بهدست میآوریم تجربه تماشای بسیار بهتری است که با بهرهگیری از CodecDB از قابلیتهای یک دستگاه خاص استفاده میکند.
And using CodecDB in code is pretty simple.
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()از الگوی سازنده مرحله روان استفاده خواهید کرد که شما را در هر پارامتر لازم راهنمایی میکند.
بنابراین مورد استفادهتان را مشخص خواهید کرد.
در اینجا از آن برای ویرایش استفاده میکنیم.
این به CodecDB میگوید که از خط لوله تراتبدیل یا صادر کردن Media3 استفاده میکنید.
برخیاز موارد استفاده دیگری که میتوانید تنظیم کنید، برای مثال، ضبط دوربین است اگر از آن برای این کار استفاده میکنید.
همچنین وضوح و نرخ فریم هدف را ارسال خواهید کرد.
سپس، بهجای حدس زدن اینکه آیا آن ویدیو 1080p به ۸ مگابیت در ثانیه یا ۱۵ مگابیت در ثانیه نیاز دارد، به CodecDB میگویید که از کدام کدک میخواهید استفاده کنید و یک هدف کیفیت ادراکی درخواست میکنید.
در این مورد، از سطح کیفیت بسیار خوب استفاده میکنیم.
در پشت صحنه، CodecDB منحنیهای محک تجربی را برای آن SOC دقیق جستجو میکند و پارامترهای کدبند و کدک ایدهآل را برمیگرداند.
انتظار داریم CodecDB بهزودی در Media3 راهاندازی شود و وقتی این اتفاق بیفتد، از انواع بومی Media3 استفاده خواهد کرد.
بنابراین ادغام آن با کد Media3 موجود شما باید بسیار آسان باشد.
بسیارخوب.
امروز عملکردهای شگفتانگیز زیادی را در Media3 دیدیم، پس بیایید خلاصه کنیم.
امروز با ماژولهای جدید Compose UI، CastPlayer جدید، خط لوله ویرایش اصلاحشده، و بسیاری از جلوههای جدیدی که میتوانید در Media3 استفاده کنید آشنا شدید.
همچنین ابزارهای جدیدی برای بازیابی فراداده و قابها دیدهایم.
و پشتیبانی از فناوری جاریسازی جدید و بهبود عملکرد و برنامههای کمکی را بررسی کردیم.
Media3 یک کتابخانه متنباز است، بنابراین میتوانید نحوه پیادهسازی همه این ویژگیها را در مخزن GitHub ما مشاهده کنید.
تا امروز، جدیدترین نسخه پایدار Media3 نسخه ۱.۱۱.۱ است.
همچنین میتوانید نسخه ۱.۱۲.۰ را که درحالحاضر در مرحله آلفا است بررسی کنید.
و این پیوند کوتاه را دنبال کنید که شما را به اسناد توسعهدهنده ما درباره Media3 میبرد.
بسیار سپاسگزاریم و از بقیه ADS لذت ببرید.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()