פתרון בעיות


תיקון שגיאות מסוג 'תנועת HTTP בטקסט גלוי לא מורשית'

השגיאה הזו תתרחש אם האפליקציה שלך תבקש תנועת HTTP לא מוצפנת (כלומר, http:// ולא https://) כשהתצורה של אבטחת הרשת לא מאפשרת זאת. אם האפליקציה מטרגטת ל-Android 9 (רמת API‏ 28) ומעלה, תנועת HTTP בטקסט גלוי מושבתת בהגדרות ברירת המחדל.

אם האפליקציה שלכם צריכה לפעול עם תנועת HTTP לא מוצפנת, אתם צריכים להשתמש בתצורה של אבטחת רשת שמאפשרת זאת. פרטים נוספים זמינים במסמכי התיעוד בנושא אבטחת רשת ב-Android. כדי לאפשר את כל תנועת ה-HTTP שאינה מוצפנת, אפשר פשוט להוסיף את המחרוזת android:usesCleartextTraffic="true" לרכיב application באפליקציה AndroidManifest.xml.

אפליקציית ההדגמה של ExoPlayer משתמשת בתצורה של אבטחת הרשת שהוגדרה כברירת מחדל, ולכן היא לא מאפשרת תנועת HTTP לא מוצפנת. אפשר להפעיל אותו באמצעות ההוראות שלמעלה.

תיקון השגיאות SSLHandshakeException,‏ CertPathValidatorException ו-ERR_CERT_AUTHORITY_INVALID

SSLHandshakeException, CertPathValidatorException ו-ERR_CERT_AUTHORITY_INVALID מציינים בעיה באישור ה-SSL של השרת. השגיאות האלה לא ספציפיות ל-ExoPlayer. פרטים נוספים זמינים במסמכי התיעוד של Android בנושא SSL.

למה אי אפשר להריץ קדימה או אחורה חלק מקובצי המדיה?

כברירת מחדל, ExoPlayer לא תומך בחיפוש במדיה שבה השיטה היחידה לביצוע פעולות חיפוש מדויקות היא סריקה ואינדוקס של הקובץ כולו על ידי הנגן. ‫ExoPlayer מתייחס לקבצים כאלה כאל קבצים שלא ניתן לחפש בהם. רוב הפורמטים המודרניים של קובצי מדיה מכילים מטא-נתונים לחיפוש (כמו אינדקס דגימות), כוללים אלגוריתם חיפוש מוגדר היטב (לדוגמה, חיפוש חציוני משולב עבור Ogg) או מציינים שהתוכן שלהם הוא קצב העברת נתונים קבוע. במקרים האלה, אפשר לבצע פעולות חיפוש יעילות, והן נתמכות על ידי ExoPlayer.

אם אתם צריכים להשתמש בתכונה 'חיפוש' אבל יש לכם מדיה שלא תומכת בה, מומלץ להמיר את התוכן לפורמט קונטיינר מתאים יותר. בקבצים מסוג MP3, ‏ ADTS ו-AMR, אפשר גם להפעיל חיפוש בהנחה שלקבצים יש קצב העברת נתונים קבוע, כפי שמתואר כאן.

למה הדילוג לא מדויק בחלק מקובצי MP3?

קובצי MP3 עם קצב העברת נתונים משתנה (VBR) לא מתאימים לתרחישי שימוש שבהם נדרשת גישה מדויקת. יש לכך שתי סיבות:

  1. כדי לדלג בצורה מדויקת, רצוי שפורמט הקונטיינר יספק מיפוי מדויק של הזמן לבית בכותרת. המיפוי הזה מאפשר לנגן למפות את הזמן המבוקש למיקום הבייט המתאים, ולהתחיל לבקש, לנתח ולהפעיל את המדיה מהמיקום הזה. לצערנו, הכותרות שזמינות לציון המיפוי הזה ב-MP3 (כמו כותרות XING) הן לרוב לא מדויקות.
  2. בפורמטים של קונטיינרים שלא מספקים מיפוי מדויק של זמן עד בייט (או מיפוי של זמן עד בייט בכלל), עדיין אפשר לבצע חיפוש מדויק אם הקונטיינר כולל חותמות זמן מוחלטות של דגימות בסטרימינג. במקרה כזה, נגן יכול למפות את זמן ההתקדמות לניחוש הטוב ביותר של היסט הבייטים המתאים, להתחיל לבקש מדיה מההיסט הזה, לנתח את חותמת הזמן הראשונה של הדגימה המוחלטת, ולבצע למעשה חיפוש בינארי מודרך במדיה עד שהוא מוצא את הדגימה הנכונה. לצערנו, קובץ MP3 לא כולל חותמות זמן מוחלטות של דגימות בסטרימינג, ולכן אי אפשר להשתמש בגישה הזו.

לכן, הדרך היחידה לבצע חיפוש מדויק בקובץ MP3 עם VBR היא לסרוק את הקובץ כולו ולבנות באופן ידני מיפוי של זמן לבית בנגן. אפשר להפעיל את האסטרטגיה הזו באמצעות FLAG_ENABLE_INDEX_SEEKING, שאפשר להגדיר אותה בDefaultExtractorsFactory באמצעות setMp3ExtractorFlags. שימו לב שהשיטה הזו לא מתאימה לקובצי MP3 גדולים, במיוחד אם המשתמש מנסה להגיע לסוף הסטרימינג זמן קצר אחרי תחילת ההפעלה. במקרה כזה, הנגן צריך להמתין עד שהסטרימינג יורד ויסתיים האינדוקס שלו לפני שהוא מבצע את ההגעה לסוף. ב-ExoPlayer, החלטנו לתת עדיפות למהירות על פני דיוק במקרה הזה, ולכן FLAG_ENABLE_INDEX_SEEKING מושבת כברירת מחדל.

אם אתם שולטים במדיה שאתם מפעילים, מומלץ מאוד להשתמש בפורמט קונטיינר מתאים יותר, כמו MP4. לא ידוע לנו על תרחישי שימוש שבהם MP3 הוא הבחירה הטובה ביותר לפורמט מדיה.

למה הניווט בסרטון איטי?

כשמבצעים חיפוש בנגן למיקום הפעלה חדש בסרטון, צריך לבצע שני דברים:

  1. טוענים את הנתונים שמתאימים למיקום ההפעלה החדש לתוך המאגר (יכול להיות שזה לא יהיה נחוץ אם הנתונים האלה כבר נמצאים במאגר).
  2. מרוקנים את מפענח הווידאו ומתחילים לפענח מה-I-frame (פריים מרכזי) לפני מיקום ההפעלה החדש, בגלל קידוד תוך-פריים שבו נעשה שימוש ברוב פורמטי הדחיסה של הווידאו. כדי לוודא שהחיפוש מדויק (כלומר, ההפעלה מתחילה בדיוק במיקום החיפוש), צריך לפענח את כל הפריימים שבין פריימים מסוג I הקודמים לבין מיקום החיפוש, ולמחוק אותם מיד (בלי להציג אותם על המסך).

כדי לצמצם את זמן האחזור שנובע מ-(1), אפשר להגדיל את כמות הנתונים שנשמרים בזיכרון על ידי הנגן, או לשמור את הנתונים במטמון בדיסק.

כדי לצמצם את זמן האחזור שנוצר בגלל (2), אפשר להקטין את רמת הדיוק של המיקום באמצעות ExoPlayer.setSeekParameters, או לקודד מחדש את הסרטון כדי שיהיו בו יותר פריימים מסוג I (מה שיגרום לקובץ פלט גדול יותר).

למה אי אפשר להפעיל חלק מקובצי MPEG-TS?

חלק מקובצי MPEG-TS לא מכילים תווי הפרדה של יחידות גישה (AUD). כברירת מחדל,‏ ExoPlayer מסתמך על יחידות AUD כדי לזהות את גבולות הפריימים בעלות נמוכה. באופן דומה, חלק מקובצי MPEG-TS לא מכילים פריימים מרכזיים של IDR. כברירת מחדל, זהו סוג הפריימים המרכזיים היחיד ש-ExoPlayer מתייחס אליו.

יכול להיות ש-ExoPlayer ייתקע במצב של אחסון בזיכרון הזמני (באפרינג) כשמבקשים ממנו להפעיל קובץ MPEG-TS שחסרים בו AUD או מסגרות מפתח IDR. אם אתם צריכים להפעיל קבצים כאלה, אתם יכולים לעשות זאת באמצעות FLAG_DETECT_ACCESS_UNITS ו-FLAG_ALLOW_NON_IDR_KEYFRAMES בהתאמה. אפשר להגדיר את הדגלים האלה ב-DefaultExtractorsFactory באמצעות setTsExtractorFlags או ב-DefaultHlsExtractorFactory באמצעות הבונה. לשימוש ב-FLAG_DETECT_ACCESS_UNITS אין תופעות לוואי, מלבד העובדה שהוא דורש הרבה משאבי מחשוב בהשוואה לזיהוי של גבולות פריימים שמבוסס על AUD. השימוש ב-FLAG_ALLOW_NON_IDR_KEYFRAMES עשוי לגרום לשיבוש חזותי זמני בתחילת ההפעלה ומיד אחרי מעברים כשמפעילים קובצי MPEG-TS מסוימים.

למה לא נמצאים כתוביות בחלק מקובצי MPEG-TS?

חלק מקובצי MPEG-TS כוללים טראקים של CEA-608, אבל הם לא מוצהרים במטא-נתונים של הקונטיינר, ולכן ExoPlayer לא יכול לזהות אותם. אפשר לציין באופן ידני כל רצועת כתוביות על ידי מתן רשימה של פורמטים צפויים של כתוביות ל-DefaultExtractorsFactory, כולל ערוצי הנגישות שאפשר להשתמש בהם כדי לזהות אותם בזרם MPEG-TS:

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory()
    .setTsSubtitleFormats(
      listOf(
        Format.Builder()
          .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
          .setAccessibilityChannel(accessibilityChannel)
          // Set other subtitle format info, such as language.
          .build()
      )
    )
val player: Player =
  ExoPlayer.Builder(context, DefaultMediaSourceFactory(context, extractorsFactory)).build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory()
        .setTsSubtitleFormats(
            ImmutableList.of(
                new Format.Builder()
                    .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
                    .setAccessibilityChannel(accessibilityChannel)
                    // Set other subtitle format info, such as language.
                    .build()));
Player player =
    new ExoPlayer.Builder(context, new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

למה חלק מקובצי MP4/FMP4 מוצגים בצורה שגויה?

חלק מקובצי MP4/FMP4 מכילים רשימות עריכה שמשכתבות את ציר הזמן של המדיה על ידי דילוג על רשימות של דגימות, הזזה שלהן או חזרה עליהן. ל-ExoPlayer יש תמיכה חלקית בהחלת רשימות עריכה. לדוגמה, הוא יכול לעכב או לחזור על קבוצות של דגימות שמתחילות בדגימת סנכרון, אבל הוא לא חותך דגימות אודיו או מפעיל מדיה לפני עריכות שלא מתחילות בדגימת סנכרון.

אם אתם רואים שחלק מהמדיה חסר או חוזר על עצמו באופן לא צפוי, נסו להגדיר את Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS או את FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS, שיגרמו לכלי החילוץ להתעלם לחלוטין מרשימות העריכה. אפשר להגדיר אותן ב-DefaultExtractorsFactory באמצעות setMp4ExtractorFlags או setFragmentedMp4ExtractorFlags.

למה חלק מהסטרימינג נכשל עם קוד תגובה 301 או 302 של HTTP?

קודים 301 ו-302 של תגובת HTTP מציינים הפניה לכתובת אחרת. תיאורים קצרים זמינים בויקיפדיה. כש-ExoPlayer שולח בקשה ומקבל תגובה עם קוד סטטוס 301 או 302, הוא בדרך כלל יפעל לפי ההפניה מחדש ויתחיל את ההפעלה כרגיל. המקרה היחיד שבו זה לא קורה כברירת מחדל הוא הפניות אוטומטיות בין פרוטוקולים. הפניה אוטומטית בין פרוטוקולים היא הפניה אוטומטית מ-HTTPS ל-HTTP או להיפך (או, לעיתים רחוקות יותר, בין זוג אחר של פרוטוקולים). כדי לבדוק אם כתובת URL גורמת להפניה אוטומטית בין פרוטוקולים, אפשר להשתמש בכלי שורת הפקודה wget באופן הבא:

wget "https://yourserver.example.com/test.mp3" 2>&1  | grep Location

הפלט אמור להיראות כך:

Location: https://secondserver.example.net/test.mp3 [following]
Location: http://thirdserver.example.org/test.mp3 [following]

בדוגמה הזו יש שתי הפניות אוטומטיות. ההפניה הראשונה היא מ-https://yourserver.example.com/test.mp3 אל https://secondserver.example.net/test.mp3. שתי הכתובות הן HTTPS, ולכן זו לא הפניה בין פרוטוקולים. ההפניה השנייה היא מ-https://secondserver.example.net/test.mp3 אל http://thirdserver.example.org/test.mp3. ההפניה האוטומטית הזו היא מ-HTTPS ל-HTTP, ולכן היא הפניה אוטומטית בין פרוטוקולים. במצב ההגדרה שמוגדר כברירת מחדל ב-ExoPlayer, ההפניה האוטומטית הזו לא תתבצע, כלומר ההפעלה תיכשל.

אם צריך, אפשר להגדיר את ExoPlayer כך שיעקוב אחרי הפניות אוטומטיות בין פרוטוקולים שונים כשיוצרים מופעים של DefaultHttpDataSource.Factory שמשמשים באפליקציה. כאן אפשר לקרוא מידע נוסף על בחירה והגדרה של מחסנית הרשת.

למה חלק מהסטרימים נכשלים עם השגיאה UnrecognizedInputFormatException?

השאלה הזו מתייחסת לבעיות בהפעלת סרטונים מהסוג הבא:

UnrecognizedInputFormatException: None of the available extractors
(MatroskaExtractor, FragmentedMp4Extractor, ...) could read the stream.

יש שתי סיבות אפשריות לכשל הזה. הסיבה הנפוצה ביותר היא שאתם מנסים להפעיל תוכן בפורמטים DASH ‏ (mpd),‏ HLS ‏ (m3u8) או SmoothStreaming ‏ (ism,‏ isml), אבל הנגן מנסה להפעיל אותו כסטרימינג מתקדם. כדי להפעיל סטרימינג כזה, צריך להסתמך על מודול ExoPlayer המתאים. במקרים שבהם ה-URI של הסטרימינג לא מסתיים בסיומת הקובץ הרגילה, אפשר גם להעביר את MimeTypes.APPLICATION_MPD, MimeTypes.APPLICATION_M3U8 או MimeTypes.APPLICATION_SS אל setMimeType של MediaItem.Builder כדי לציין במפורש את סוג הסטרימינג.

הסיבה השנייה, שהיא פחות נפוצה, היא ש-ExoPlayer לא תומך בפורמט הקונטיינר של המדיה שאתם מנסים להפעיל. במקרה הזה, הכשל פועל כמצופה, אבל אפשר לשלוח בקשה להוספת תכונה לכלי למעקב אחר בעיות שלנו, כולל פרטים על פורמט הקונטיינר וזרם בדיקה. לפני ששולחים בקשה חדשה לתכונה, כדאי לחפש בקשות קיימות.

למה הפונקציה setPlaybackParameters לא פועלת כמו שצריך בחלק מהמכשירים?

כשמריצים גרסת ניפוי באגים של האפליקציה ב-Android M ובגרסאות קודמות, יכול להיות שתיתקלו בביצועים לא חלקים, בארטיפקטים שניתן לשמוע ובשימוש גבוה במעבד (CPU) כשמשתמשים ב-API‏ setPlaybackParameters. הסיבה לכך היא שאופטימיזציה שחשובה ל-API הזה מושבתת בגרסאות build לניפוי באגים שפועלות בגרסאות האלה של Android.

חשוב לציין שהבעיה הזו משפיעה רק על גרסאות ניפוי באגים. הוא לא משפיע על גרסאות build של הפצה, שבהן האופטימיזציה תמיד מופעלת. לכן, הבעיה הזו לא אמורה להשפיע על הגרסאות שאתם מספקים למשתמשי הקצה.

מה המשמעות של השגיאות 'הגישה לנגן מתבצעת בשרשור שגוי'?

מידע נוסף זמין בקטע הערה על שרשור בדף תחילת העבודה.

איך אפשר לפתור את הבעיה 'שורת סטטוס לא צפויה: ICY 200 OK'?

הבעיה הזו יכולה להתרחש אם תגובת השרת כוללת שורת סטטוס ICY, ולא שורת סטטוס שתואמת ל-HTTP. השימוש בשורות הסטטוס ICY יצא משימוש ואין להשתמש בהן. לכן, אם יש לכם שליטה בשרת, עליכם לעדכן אותו כך שיספק תגובה שתואמת ל-HTTP. אם לא הצלחתם לעשות את זה, שימוש בספריית ExoPlayer OkHttp יפתור את הבעיה, כי היא יודעת לטפל בשורות סטטוס של ICY בצורה נכונה.

איך אפשר לשאול אם הזרם שמופעל הוא שידור חי?

אפשר לשלוח שאילתה לשיטה isCurrentWindowLive של הנגן. בנוסף, אפשר לסמן את התיבה isCurrentWindowDynamic כדי לבדוק אם חלון ההמרות דינמי (כלומר, עדיין מתעדכן לאורך זמן).

איך אפשר להמשיך להפעיל אודיו כשהאפליקציה פועלת ברקע?

כדי לוודא שההפעלה של האודיו תימשך כשהאפליקציה פועלת ברקע:

  1. צריך להפעיל שירות שפועל בחזית. כך המערכת לא תפסיק את התהליך כדי לפנות משאבים.
  2. אתם צריכים להיות בעלי WifiLock וWakeLock. הם מבטיחים שמערכת ההפעלה תשאיר את רכיב ה-Wi-Fi ואת המעבד במצב פעיל. אם משתמשים ב-ExoPlayer, אפשר לעשות זאת בקלות על ידי קריאה ל-setWakeMode, שתשיג ותשחרר אוטומטית את הנעילות הנדרשות בזמנים הנכונים.

חשוב לשחרר את הנעילות (אם לא משתמשים ב-setWakeMode) ולהפסיק את השירות ברגע שהשמע לא מושמע יותר.

למה ExoPlayer תומך בתוכן שלי אבל ספריית ExoPlayer Cast לא?

יכול להיות שהתוכן שאתם מנסים להפעיל לא תומך ב-CORS. כדי להפעיל תוכן באמצעות Cast framework, צריך להפעיל את ה-CORS שלו.

למה הפעלת התוכן נכשלת, אבל לא מוצגת שגיאה?

יכול להיות שהמכשיר שבו מופעל התוכן לא תומך בפורמט מסוים של דגימת מדיה. אפשר לוודא את זה בקלות על ידי הוספת EventLogger כמאזין לנגן, וחיפוש שורה דומה לזו ב-Logcat:

[ ] Track:x, id=x, mimeType=mime/type, ... , supported=NO_UNSUPPORTED_TYPE

הערך NO_UNSUPPORTED_TYPE מציין שהמכשיר לא יכול לפענח את פורמט דגימת המדיה שצוין על ידי mimeType. מידע על פורמטים נתמכים של דוגמאות זמין במסמכי התיעוד בנושא פורמטים של מדיה ב-Android. אולי יעניין אותך גם המאמר איך אפשר להשיג ספריית פענוח כדי לטעון אותה ולהשתמש בה להשמעה?

איך אפשר לטעון ספריית פענוח ולהשתמש בה להפעלה?

  • לרוב ספריות הפענוח יש שלבים ידניים להוצאת התלות ולבנייתה, לכן חשוב לוודא שפעלתם לפי השלבים בקובץ ה-README של הספרייה הרלוונטית. לדוגמה, כדי להשתמש בספריית ExoPlayer FFmpeg, צריך לפעול לפי ההוראות שבקובץ libraries/decoder_ffmpeg/README.md, כולל העברת דגלי הגדרה כדי להפעיל קודקים לכל הפורמטים שרוצים להפעיל.
  • בספריות שיש בהן קוד מקורי, צריך לוודא שמשתמשים בגרסה הנכונה של Android NDK, כפי שמצוין בקובץ ה-README, ולשים לב לשגיאות שמופיעות במהלך ההגדרה והבנייה. אחרי שמבצעים את השלבים בקובץ ה-README, אמורים לראות קבצים .so בספריית המשנה libs של נתיב הספרייה לכל ארכיטקטורה נתמכת.
  • כדי לנסות להפעיל תוכן באמצעות הספרייה באפליקציית ההדגמה, אפשר לעיין במאמר בנושא הפעלת מפענחים בחבילה. הוראות לשימוש בספרייה מתוך האפליקציה שלכם מופיעות בקובץ ה-README של הספרייה.
  • אם אתם משתמשים ב-DefaultRenderersFactory, אמור להופיע ב-Logcat קו יומן ברמת המידע, כמו 'Loaded FfmpegAudioRenderer', כשהמפענח נטען. אם הוא חסר, צריך לוודא שהאפליקציה תלויה בספריית הפענוח.
  • אם מופיעים יומנים ברמת אזהרה מ-LibraryLoader ב-Logcat, המשמעות היא שהטעינה של רכיב ה-Native בספרייה נכשלה. אם זה קורה, צריך לוודא שפעלתם לפי השלבים בקובץ ה-README של הספרייה בצורה נכונה, ושלא הוצגו שגיאות במהלך הפעולה לפי ההוראות.

אם אתם עדיין נתקלים בבעיות בשימוש בספריות פענוח, כדאי לבדוק בכלי למעקב אחרי בעיות של Media3 אם יש בעיות רלוונטיות מהזמן האחרון. אם אתם צריכים לדווח על בעיה חדשה שקשורה ליצירת החלק המקורי של הספרייה, עליכם לכלול את הפלט המלא של שורת הפקודה מהפעלת ההוראות בקובץ ה-README, כדי שנוכל לאבחן את הבעיה.

האם אפשר להפעיל סרטונים ב-YouTube ישירות באמצעות ExoPlayer?

לא, אי אפשר להפעיל סרטונים מ-YouTube ב-ExoPlayer, כמו כתובות URL בפורמט https://www.youtube.com/watch?v=.... במקום זאת, צריך להשתמש ב-YouTube IFrame Player API, שהוא הדרך הרשמית להפעיל סרטוני YouTube ב-Android.

הפעלת הסרטון מגמגמת

יכול להיות שהמכשיר לא יוכל לפענח את התוכן מספיק מהר אם, לדוגמה, קצב העברת הנתונים או הרזולוציה של התוכן חורגים מהיכולות של המכשיר. יכול להיות שתצטרכו להשתמש בתוכן באיכות נמוכה יותר כדי לקבל ביצועים טובים במכשירים כאלה.

אם אתם נתקלים בגימגום של סרטונים במכשיר עם Android מגרסה Android 6.0 (רמת API‏ 23) עד גרסה Android 11 (רמת API‏ 30) כולל, במיוחד כשמפעילים תוכן שמוגן על ידי DRM או תוכן עם קצב פריימים גבוה, אתם יכולים לנסות להפעיל תור מאגר אסינכרוני.

שגיאות לא יציבות של API lint

‫Media3 מבטיח תאימות בינארית לחלק מממשק ה-API. החלקים שלא מבטיחים תאימות בינארית מסומנים ב-@UnstableApi. כדי להבהיר את ההבחנה הזו, שימוש בסמלי API לא יציבים יוצר שגיאת lint, אלא אם הם מסומנים בהערה @OptIn.

ההערה @UnstableApi לא מרמזת על איכות או ביצועים של API, אלא רק על העובדה שהוא לא 'מוקפא'.

יש שתי אפשרויות לטיפול בשגיאות לא יציבות של API lint:

  • עוברים לשימוש ב-API יציב שמשיג את אותה התוצאה.
  • ממשיכים להשתמש ב-API הלא יציב ומציינים את השימוש באמצעות @OptIn, כמו שמוצג בהמשך.
הוספת ההערה @OptIn

אתם יכולים להשתמש ב-Android Studio כדי להוסיף את ההערה:

צילום מסך: איך מוסיפים את הערת ההסכמה
איור 2: הוספת הערה (annotation) מסוג @androidx.annotations.OptIn באמצעות Android Studio.

אפשר גם להוסיף הערות ידניות לאתרים ספציפיים שבהם נעשה שימוש ב-Kotlin:

import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi

@OptIn(UnstableApi::class)
fun functionUsingUnstableApi() { ... }

וגם ב-Java:

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

@OptIn(markerClass = UnstableApi.class)
private void methodUsingUnstableApis() { ... }

כדי להביע הסכמה לשימוש בחבילות שלמות, מוסיפים קובץ package-info.java:

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

אפשר להפעיל את ההגדרה הזו לכל הפרויקטים על ידי השבתת שגיאת ה-lint הספציפית בקובץ lint.xml:

 <?xml version="1.0" encoding="utf-8"?>
 <lint>
   <issue id="UnsafeOptInUsageError">
     <option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
   </issue>
 </lint>

יש גם הערה kotlin.OptIn שאסור להשתמש בה. חשוב להשתמש בהערה androidx.annotation.OptIn.