פתרון בעיות


תיקון שגיאות מסוג 'תנועת 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.

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

קוד תגובת HTTP‏ 301 וקוד תגובת HTTP‏ 302 מציינים הפניה לכתובת אחרת. תיאורים קצרים אפשר למצוא בוויקיפדיה. כש-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 לא פועלת כמו שצריך בחלק מהמכשירים?

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

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

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

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

איך אפשר לפתור את הבעיה 'שורת סטטוס לא צפויה: 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, כולל העברת דגלי הגדרה כדי להפעיל את פענוח הקוד לכל הפורמטים שרוצים להפעיל.
  • בספריות שיש בהן קוד Native, צריך לוודא שמשתמשים בגרסה הנכונה של Android NDK, כפי שמצוין בקובץ ה-README, ולשים לב לשגיאות שמופיעות במהלך ההגדרה והבנייה. אחרי שמבצעים את השלבים בקובץ ה-README, אמורים לראות קבצים .so בספריית המשנה libs של נתיב הספרייה לכל ארכיטקטורה נתמכת.
  • כדי לנסות להפעיל תוכן באמצעות הספרייה באפליקציית ההדגמה, אפשר לעיין במאמר בנושא הפעלת מפענחים בחבילה. הוראות לשימוש בספרייה מתוך האפליקציה שלכם מופיעות בקובץ ה-README של הספרייה.
  • אם אתם משתמשים ב-DefaultRenderersFactory, אמור להופיע ב-Logcat קו יומן ברמת המידע, כמו 'Loaded FfmpegAudioRenderer', כשהמפענח נטען. אם הוא חסר, צריך לוודא שהאפליקציה תלויה בספריית הפענוח.
  • אם אתם רואים ב-Logcat יומנים ברמת אזהרה מ-LibraryLoader, זה מצביע על כך שהטעינה של רכיב ה-native של הספרייה נכשלה. אם זה קורה, צריך לוודא שפעלתם לפי השלבים בקובץ ה-README של הספרייה בצורה נכונה, ושלא הוצגו שגיאות במהלך הפעולות.

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

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

לא, ל-ExoPlayer אין אפשרות להפעיל סרטונים מ-YouTube, כמו כתובות 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:

Kotlin

// In your package-info.kt
@OptIn(UnstableApi::class)
package name.of.your.package

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

Java

// In your 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.