افزودن ویدیو با استفاده از تصویر در تصویر (PiP)

با شروع Android 8.0 (سطح API 26)، Android اجازه می دهد تا فعالیت ها در حالت تصویر در تصویر (PiP) راه اندازی شوند. PiP نوع خاصی از حالت چند پنجره ای است که بیشتر برای پخش ویدیو استفاده می شود. این امکان را به کاربر می دهد که در یک پنجره کوچک که به گوشه ای از صفحه سنجاق شده است، هنگام حرکت بین برنامه ها یا مرور محتوا در صفحه اصلی، ویدیویی را تماشا کند.

PiP از APIهای چندپنجره‌ای که در Android 7.0 در دسترس هستند برای ارائه پنجره همپوشانی ویدئویی پین شده استفاده می‌کند. برای افزودن PiP به برنامه خود، باید فعالیت‌های خود را که از PiP پشتیبانی می‌کنند ثبت کنید، فعالیت خود را در صورت نیاز به حالت PiP تغییر دهید، و مطمئن شوید که عناصر رابط کاربری پنهان هستند و پخش ویدیو زمانی که فعالیت در حالت PiP است ادامه می‌یابد.

پنجره PiP در بالاترین لایه صفحه نمایش، در گوشه ای که توسط سیستم انتخاب شده ظاهر می شود.

PiP همچنین در دستگاه‌های سازگار با سیستم‌عامل Android TV دارای Android 14 (سطح API 34) یا بالاتر پشتیبانی می‌شود. در حالی که شباهت های زیادی وجود دارد، هنگام استفاده از PiP در تلویزیون ملاحظات دیگری نیز وجود دارد.

نحوه تعامل کاربران با پنجره PiP

کاربران می توانند پنجره PiP را به مکان دیگری بکشند. با شروع اندروید 12، کاربران همچنین می توانند:

  • برای نمایش یک کلید تمام صفحه، یک دکمه بستن، یک دکمه تنظیمات و اقدامات سفارشی ارائه شده توسط برنامه شما (به عنوان مثال، کنترل های پخش) روی پنجره یک ضربه بزنید.

  • برای جابه‌جایی بین اندازه PiP فعلی و حداکثر یا حداقل اندازه PiP، دو بار روی پنجره ضربه بزنید - برای مثال، دو بار ضربه زدن روی یک پنجره حداکثر آن را به حداقل می‌رساند و عکس آن نیز صادق است.

  • پنجره را با کشیدن آن به لبه چپ یا راست پنهان کنید. برای باز کردن پنجره، روی قسمت قابل مشاهده پنجره پنهان شده ضربه بزنید یا آن را به بیرون بکشید.

  • اندازه پنجره PiP را با استفاده از کوچک کردن به زوم تغییر دهید.

برنامه شما زمانی را کنترل می کند که فعالیت فعلی وارد حالت PiP شود. در اینجا چند نمونه آورده شده است:

  • زمانی که کاربر روی دکمه هوم ضربه می‌زند یا به سمت خانه حرکت می‌کند، یک فعالیت می‌تواند وارد حالت PiP شود. به این ترتیب Google Maps به نمایش مسیرها ادامه می دهد در حالی که کاربر فعالیت دیگری را همزمان انجام می دهد.

  • هنگامی که کاربر از ویدیو برای مرور محتواهای دیگر بازمی گردد، برنامه شما می تواند یک ویدیو را به حالت PiP منتقل کند.

  • برنامه شما می‌تواند یک ویدیو را به حالت PiP تغییر دهد در حالی که کاربر پایان یک قسمت از محتوا را تماشا می‌کند. صفحه اصلی اطلاعات تبلیغاتی یا خلاصه ای از قسمت بعدی سریال را نمایش می دهد.

  • برنامه شما می‌تواند راهی برای کاربران فراهم کند تا در هنگام تماشای ویدیو، محتوای اضافی را در صف قرار دهند. پخش ویدیو در حالت PiP ادامه می یابد در حالی که صفحه اصلی فعالیت انتخاب محتوا را نشان می دهد.

پشتیبانی PiP را اعلام کنید

به طور پیش فرض، سیستم به طور خودکار از PiP برای برنامه ها پشتیبانی نمی کند. اگر می‌خواهید از PiP در برنامه خود پشتیبانی کنید، با تنظیم android:supportsPictureInPicture روی true ، فعالیت ویدیویی خود را در مانیفست خود ثبت کنید. همچنین، مشخص کنید که فعالیت شما تغییرات پیکربندی چیدمان را انجام دهد تا زمانی که تغییرات طرح‌بندی در طول انتقال حالت PiP رخ می‌دهد، فعالیت شما دوباره راه‌اندازی نشود.

<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation"
    ...

فعالیت خود را به PiP تغییر دهید

با شروع Android 12، می‌توانید با تنظیم پرچم setAutoEnterEnabled روی true ، فعالیت خود را به حالت PiP تغییر دهید. با این تنظیم، یک اکتیویتی در صورت نیاز به طور خودکار به حالت PiP تغییر می کند، بدون اینکه نیازی به فراخوانی صریح enterPictureInPictureMode() در onUserLeaveHint باشد. و این مزیت افزوده ارائه انتقال بسیار نرمتر را دارد. برای جزئیات، از پیمایش اشاره ای، انتقال به حالت PiP را هموارتر کنید .

اگر Android 11 یا پایین‌تر را هدف قرار می‌دهید، یک فعالیت باید enterPictureInPictureMode() را فراخوانی کند تا به حالت PiP بروید. برای مثال، زمانی که کاربر روی دکمه اختصاصی در رابط کاربری برنامه کلیک می‌کند، کد زیر یک فعالیت را به حالت PiP تغییر می‌دهد:

کاتلین

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

جاوا

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

ممکن است بخواهید منطقی را وارد کنید که یک فعالیت را به جای رفتن به پس‌زمینه به حالت PiP تغییر می‌دهد. به عنوان مثال، اگر کاربر در حین پیمایش برنامه، دکمه خانه یا موارد اخیر را فشار دهد، Google Maps به حالت PiP تغییر می کند. می توانید این مورد را با نادیده گرفتن onUserLeaveHint() پیدا کنید:

کاتلین

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

جاوا

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

توصیه می‌شود: تجربه انتقال PiP را به کاربران ارائه دهید

اندروید 12 بهبودهای زیبایی قابل توجهی را به انتقال متحرک بین پنجره های تمام صفحه و PiP اضافه کرد. ما قویاً توصیه می کنیم همه تغییرات قابل اجرا را اجرا کنید. هنگامی که این کار را انجام دادید، این تغییرات به طور خودکار به صفحه نمایش های بزرگ مانند تاشوها و تبلت ها بدون نیاز به کار بیشتر تبدیل می شوند.

اگر برنامه شما شامل به‌روزرسانی‌های قابل‌اجرا نباشد، انتقال PiP همچنان کاربردی است، اما انیمیشن‌ها کمتر صیقلی هستند. به عنوان مثال، انتقال از حالت تمام صفحه به حالت PiP می تواند باعث شود که پنجره PiP در طول انتقال قبل از اینکه پس از تکمیل انتقال دوباره ظاهر شود، ناپدید شود.

این تغییرات شامل موارد زیر است.

  • از پیمایش اشاره ای، انتقال به حالت PiP هموارتر می شود
  • تنظیم یک sourceRectHint مناسب برای ورود و خروج از حالت PiP
  • غیرفعال کردن تغییر اندازه یکپارچه برای محتوای غیر ویدئویی

به نمونه Android Kotlin PictureInPicture به عنوان مرجع برای فعال کردن یک تجربه انتقال صیقلی مراجعه کنید.

از پیمایش اشاره ای، انتقال به حالت PiP را روان تر کنید

با شروع در اندروید 12، پرچم setAutoEnterEnabled انیمیشن بسیار نرم تری را برای انتقال به محتوای ویدیویی در حالت PiP با استفاده از ناوبری حرکتی ارائه می دهد - به عنوان مثال، هنگام کشیدن انگشت به سمت خانه از تمام صفحه به سمت بالا.

برای ایجاد این تغییر مراحل زیر را کامل کنید و برای مرجع به این نمونه مراجعه کنید:

  1. برای ساخت PictureInPictureParams.Builder از setAutoEnterEnabled استفاده کنید:

    کاتلین

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    جاوا

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. با PictureInPictureParams به‌روز، زودتر با setPictureInPictureParams تماس بگیرید. برنامه منتظر تماس onUserLeaveHint نمی ماند (همانطور که در اندروید 11 انجام می داد).

    به عنوان مثال، ممکن است بخواهید setPictureInPictureParams در اولین پخش و در صورت تغییر نسبت ابعاد، هر پخش بعدی را فراخوانی کنید.

  3. با setAutoEnterEnabled(false) تماس بگیرید، اما فقط در صورت لزوم. به عنوان مثال، اگر پخش فعلی در حالت مکث است، احتمالاً نمی خواهید PiP را وارد کنید.

یک sourceRectHint مناسب برای ورود و خروج از حالت PiP تنظیم کنید

با شروع با معرفی PiP در Android 8.0، setSourceRectHint ناحیه‌ای از فعالیت را نشان داد که پس از انتقال به تصویر در تصویر قابل مشاهده است - به عنوان مثال، محدودیت‌های نمای ویدیو در پخش‌کننده ویدیو.

با اندروید 12، سیستم از sourceRectHint برای پیاده سازی انیمیشن بسیار روان تری هم هنگام ورود و هم در هنگام خروج از حالت PiP استفاده می کند.

برای تنظیم صحیح sourceRectHint برای ورود و خروج از حالت PiP:

  1. PictureInPictureParams با استفاده از کرانهای مناسب به عنوان sourceRectHint بسازید. همچنین توصیه می‌کنیم شنونده تغییر طرح‌بندی را به پخش‌کننده ویدیو پیوست کنید:

    کاتلین

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    جاوا

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  2. در صورت لزوم، قبل از اینکه سیستم انتقال خروج را شروع کند، sourceRectHint را به روز کنید. هنگامی که سیستم در شرف خروج از حالت PiP است، سلسله مراتب نمای فعالیت به پیکربندی مقصد (مثلاً تمام صفحه) نشان داده می شود. این برنامه می‌تواند شنونده تغییر طرح‌بندی را به نمای اصلی یا نمای هدف (مانند نمای پخش‌کننده ویدیو) متصل کند تا رویداد را شناسایی کرده و sourceRectHint قبل از شروع انیمیشن به‌روزرسانی کند.

    کاتلین

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            val sourceRectHint = Rect()
            playerView.getGlobalVisibleRect(sourceRectHint)
            setPictureInPictureParams(
                PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build()
            )
        }
    }
    
    

    جاوا

    // Listener is called right after the user exits PiP but before
    // animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });
    
    

تغییر اندازه یکپارچه را برای محتوای غیر ویدئویی غیرفعال کنید

اندروید 12 پرچم setSeamlessResizeEnabled را اضافه می کند که در هنگام تغییر اندازه محتوای غیر ویدئویی در پنجره PiP، انیمیشن متقاطع بسیار نرم تری ارائه می دهد. قبلاً، تغییر اندازه محتوای غیر ویدئویی در یک پنجره PiP می‌توانست مصنوعات بصری دردناکی ایجاد کند.

برای غیرفعال کردن تغییر اندازه یکپارچه برای محتوای غیر ویدئویی:

کاتلین

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build())

جاوا

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build());

مدیریت UI در حین PiP

هنگامی که اکتیویتی وارد حالت PiP یا خارج می شود، سیستم Activity.onPictureInPictureModeChanged() یا Fragment.onPictureInPictureModeChanged() را فراخوانی می کند.

برای ترسیم مجدد عناصر UI فعالیت، باید این تماس‌های برگشتی را لغو کنید. به خاطر داشته باشید که در حالت PiP فعالیت شما در یک پنجره کوچک نشان داده می شود. وقتی برنامه شما در حالت PiP است، کاربران نمی‌توانند با عناصر رابط کاربری برنامه شما تعامل داشته باشند و دیدن جزئیات عناصر رابط کاربری کوچک ممکن است دشوار باشد. فعالیت های پخش ویدیو با حداقل رابط کاربری بهترین تجربه کاربر را ارائه می دهد.

اگر برنامه شما نیاز به ارائه اقدامات سفارشی برای PiP دارد، به افزودن کنترل‌ها در این صفحه مراجعه کنید. قبل از اینکه فعالیت شما وارد PiP شود، سایر عناصر رابط کاربری را حذف کنید و وقتی فعالیت شما دوباره تمام صفحه شد، آنها را بازیابی کنید:

کاتلین

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
    }
}

جاوا

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

کنترل ها را اضافه کنید

وقتی کاربر منوی پنجره را باز می‌کند، پنجره PiP می‌تواند کنترل‌ها را نمایش دهد (با ضربه زدن روی پنجره روی یک دستگاه تلفن همراه یا انتخاب منو از کنترل از راه دور تلویزیون).

اگر برنامه‌ای یک جلسه رسانه فعال داشته باشد، کنترل‌های پخش، مکث، بعدی و قبلی ظاهر می‌شوند.

همچنین می‌توانید با ساخت PictureInPictureParams با PictureInPictureParams.Builder.setActions() قبل از وارد شدن به حالت PiP، اکشن‌های سفارشی را مشخص کنید و هنگام ورود به حالت PiP با استفاده از enterPictureInPictureMode(android.app.PictureInPictureParams) یا setPictureInPictureParams(android.app.PictureInPictureParams) . مراقب باشید. اگر بخواهید بیشتر از getMaxNumPictureInPictureActions() اضافه کنید، فقط حداکثر عدد را دریافت خواهید کرد.

ادامه پخش ویدیو در حالی که در PiP هستید

هنگامی که فعالیت شما به PiP تغییر می کند، سیستم اکتیویتی را در حالت مکث قرار می دهد و متد onPause() فعالیت را فراخوانی می کند. پخش ویدیو نباید متوقف شود و در عوض اگر فعالیت در حین انتقال به حالت PiP متوقف شد، به پخش ادامه دهید.

در Android نسخه 7.0 و بالاتر، زمانی که سیستم onStop() و onStart() فعالیت شما را فراخوانی می‌کند، باید پخش ویدیو را متوقف کرده و از سر بگیرید. با انجام این کار، می‌توانید از بررسی اینکه آیا برنامه شما در حالت PiP در onPause() است و صریحاً به پخش ادامه می‌دهد، اجتناب کنید.

اگر پرچم setAutoEnterEnabled روی true تنظیم نکرده‌اید و باید پخش را در اجرای onPause() متوقف کنید، حالت PiP را با فراخوانی isInPictureInPictureMode() بررسی کنید و پخش را به طور مناسب مدیریت کنید. به عنوان مثال:

کاتلین

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode) {
        // Continue playback
    } else {
        // Use existing playback logic for paused Activity behavior.
    }
}

جاوا

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode()) {
        // Continue playback
        ...
    } else {
        // Use existing playback logic for paused Activity behavior.
        ...
    }
}

هنگامی که فعالیت شما از حالت PiP به حالت تمام صفحه خارج می شود، سیستم فعالیت شما را از سر می گیرد و متد onResume() شما را فراخوانی می کند.

از یک فعالیت پخش واحد برای PiP استفاده کنید

در برنامه شما، کاربر ممکن است هنگام مرور محتوا در صفحه اصلی، ویدیوی جدیدی را انتخاب کند، در حالی که یک فعالیت پخش ویدیو در حالت PiP است. به جای راه اندازی یک فعالیت جدید که ممکن است کاربر را سردرگم کند، ویدیوی جدید را در فعالیت پخش موجود در حالت تمام صفحه پخش کنید.

برای اطمینان از اینکه یک اکتیویتی برای درخواست‌های پخش ویدیو استفاده می‌شود و در صورت نیاز به حالت PiP یا خارج می‌شود، android:launchMode فعالیت را روی singleTask در مانیفست خود تنظیم کنید:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

در فعالیت خود، onNewIntent() را نادیده بگیرید و ویدیوی جدید را مدیریت کنید و در صورت نیاز پخش ویدیوی موجود را متوقف کنید.

بهترین شیوه ها

PiP ممکن است در دستگاه‌هایی که RAM پایینی دارند غیرفعال شود. قبل از اینکه برنامه شما از PiP استفاده کند، با تماس با hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) مطمئن شوید که در دسترس است.

PiP برای فعالیت هایی در نظر گرفته شده است که ویدیوهای تمام صفحه را پخش می کنند. هنگامی که فعالیت خود را به حالت PiP تغییر می دهید، از نمایش چیزی به جز محتوای ویدیویی خودداری کنید. زمانی که فعالیت شما وارد حالت PiP می شود، ردیابی کنید و عناصر UI را پنهان کنید، همانطور که در Handling UI در طول PiP توضیح داده شده است.

وقتی یک فعالیت در حالت PiP است، به طور پیش‌فرض فوکوس ورودی را دریافت نمی‌کند. برای دریافت رویدادهای ورودی در حالت PiP، از MediaSession.setCallback() استفاده کنید. برای اطلاعات بیشتر در مورد استفاده از setCallback() به نمایش کارت در حال پخش مراجعه کنید.

وقتی برنامه شما در حالت PiP است، پخش ویدیو در پنجره PiP می‌تواند باعث تداخل صوتی با برنامه دیگری، مانند برنامه پخش‌کننده موسیقی یا برنامه جستجوی صوتی شود. برای جلوگیری از این امر، هنگام شروع پخش ویدیو، فوکوس صوتی را درخواست کنید و همانطور که در مدیریت فوکوس صوتی توضیح داده شده است، اعلان‌های تغییر فوکوس صوتی را مدیریت کنید. اگر در حالت PiP اعلان از دست دادن فوکوس صوتی دریافت کردید، پخش ویدیو را متوقف یا متوقف کنید.

هنگامی که برنامه شما در شرف ورود به PiP است، توجه داشته باشید که تنها فعالیت برتر وارد تصویر در تصویر می شود. در برخی موقعیت‌ها مانند دستگاه‌های چند پنجره‌ای، ممکن است فعالیت زیر اکنون در کنار فعالیت PiP نشان داده شود و دوباره قابل مشاهده باشد. شما باید این مورد را بر این اساس مدیریت کنید، از جمله فعالیت زیر دریافت یک onResume() یا یک پاسخ onPause() . همچنین ممکن است کاربر با فعالیت تعامل داشته باشد. به عنوان مثال، اگر یک فعالیت لیست ویدیویی نمایش داده شده باشد و فعالیت ویدیویی در حال پخش در حالت PiP باشد، کاربر ممکن است یک ویدیوی جدید را از لیست انتخاب کند و فعالیت PiP باید بر این اساس به روز شود.

کد نمونه اضافی

برای دانلود یک برنامه نمونه نوشته شده در Kotlin، به Android PictureInPicture Sample (Kotlin) مراجعه کنید.