פיתוח Reels (סרטונים קצרים) באפליקציה למובייל
הלקוח מתלונן: פיד הווידאו האנכי מגמגם באייפון 8 עם iOS ישן — המעבר בין קליפים לוקח שנייה, האודיו לא מסונכרן, וסוויפים מהירים מקריסים את האפליקציה עקב לחץ על הזיכרון. נשמע מוכר? אנחנו פותרים את זה: אנו מיישמים גלילת עמודים חלקה באמצעות נגן משותף, טעינה מוקדמת של וידאו שלוש עמדות קדימה, ושמירה במטמון של AVPlayerItem לחמישה קליפים שכנים — כל זה מספק 60 fps יציבים אפילו במכשירים עם 2 GB RAM.
פידים של וידאו אנכי הם טכנית אחד מתבניות ה-UI המאתגרות ביותר: הם דורשים סנכרון אודיו-וידאו, מזעור השהיית גלילה, וניהול זיכרון נכון. הצוות שלנו בנה למעלה מ-50 פרויקטים של פיד וידאו באמצעות Swift 5.9+, Kotlin 1.9+, Combine ו-Coroutines. המהנדסים שלנו מוסמכים על ידי אפל וגוגל, עם ניסיון עמוק בהנחיות App Store Review (סעיפים 4.2, 5.1) וב-Google Play Console. הפתרון המרכזי: שימוש במופע יחיד של AVPlayer (iOS) או ExoPlayer (Android) המוקצה מחדש בין תאים. זה מבטל עיכובים באתחול נגן ומפחית עומס על ה-GPU. טעינה מוקדמת של AVPlayerItem שלושה סרטונים קדימה מקצרת את זמן המעבר ל-80 אלפיות השנייה. צרו קשר להערכת עלות.
דרישות טכניות למכשיר
- iOS 14+, Android 7.0+
- מינימום 2 GB RAM
- תמיכה ב-VideoToolbox (iOS) / MediaCodec (Android)
גלילת עמודים אנכית
הבסיס של Reels הוא UICollectionView עם UICollectionViewCompositionalLayout + UICollectionLayoutSectionOrthogonalScrollingBehavior.paging (iOS) או RecyclerView עם PagerSnapHelper (Android). כל תא ממלא את כל המסך (bounds.size).
ב-iOS, גלילת עמודים דרך UICollectionView עם isPagingEnabled = true עובדת אך חסרה שליטה על האטה. עדיף: UICollectionViewFlowLayout מותאם אישית שעוקף את targetContentOffset(forProposedContentOffset:withScrollingVelocity:) ונצמד לווידאו הקרוב עם האנימציה הרצויה.
מעבר מבוסס מהירות: סוויפ מהיר עובר ל-Reel הבא ללא קשר למרחק הגלילה. sqrt(velocity.x² + velocity.y²) > threshold → הצמדה לתא הבא.
איך להחליף סרטונים ללא עיכוב?
ההחלטה הקריטית: AVPlayer משותף אחד עם AVPlayerItem המוקצה מחדש במהלך הגלילה. ב-scrollViewDidEndDecelerating, אנחנו לוקחים את הנגן מהתא הקודם ומקצים אותו לתא הנוכחי. AVPlayerLayer.player = avPlayer — מיידי. הנגן אף פעם לא נוצר מחדש — המעבר מתרחש ללא הפסקות.
ב-Android — ExoPlayer אחד לכל ה-Activity/Fragment. PlayerView.player = exoPlayer מקצה מחדש. אנו משתמשים ב-MediaItem עם טעינה מוקדמת: exoPlayer.addMediaItem(nextMediaItem) עבור הווידאו הבא — ExoPlayer מתחיל לטעון לפני המעבר בפועל. ExoPlayer עם טעינה מוקדמת מהיר פי 4 מ-MediaPlayer הסטנדרטי.
טעינה מוקדמת: שמירה בזיכרון של AVPlayerItem עבור הסרטון הנוכחי, הבא והקודם. יצירת AVPlayerItem מ-URL לוקחת זמן — אנחנו עושים זאת בתחילת הגלילה לכיוון תא שכן, לא בסיומה.
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Предзагружаем AVPlayerItem для этого индекса
let item = AVPlayerItem(url: videoURLs[indexPath.row])
playerItemCache[indexPath.row] = item
}לפי מדריך AVFoundation של אפל, גישה זו ממזערת עיכובים.
איך ליישם הקלטה מרובת קטעים?
המצלמה להקלטת Reels היא func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { // Предзагружаем AVPlayerItem для этого индекса let item = AVPlayerItem(url: videoURLs[indexPath.row]) playerItemCache[indexPath.row] = item } מותאם אישית. AVCaptureSession מציג תצוגה מקדימה חיה במסך מלא. הקלטה דרך AVCaptureVideoPreviewLayer עם מגבלה: AVCaptureMovieFileOutput.
הקלטה מרובת קטעים (מספר קליפים קצרים ל-Reel אחד) — יצירת maxRecordedDuration = CMTime(seconds: 60, preferredTimescale: 600) נפרד לכל טייק, ולאחר מכן מיזוג דרך AVAsset. AVMutableComposition מכניס קטעים ברצף.
פילטרים בזמן אמת — AVMutableCompositionTrack.insertTimeRange() עם Core Image המוחל על CIFilter במתודת ה-delegate CMSampleBuffer. עיבוד דרך captureOutput(_:didOutput:from:) עם Metal (CIContext). OpenGL למשימה זו מיושן עם ביצועים גרועים יותר.
סאונד, טקסט, אפקטים
רצועת אודיו — מוזיקת המשתמש מספריית המדיה דרך CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!) (iOS) או MPMediaPickerController עם ACTION_PICK (Android), או רצועות מקטלוג האפליקציה. הנחת אודיו מעל וידאו באמצעות MediaStore.Audio עם שתי רצועות: וידאו + אודיו.
שכבת טקסט — לא AVMutableComposition על גבי UILabel, אלא מוטבעת בווידאו דרך UIImageView עם AVVideoCompositionCoreAnimationTool. זה מבטיח עיבוד נכון של טקסט במהלך ייצוא ופרסום.
ביצועי הפיד
CATextLayer ב-iOS — בקשת מטא-דאטה של 3 הסרטונים הבאים במהלך הגלילה. UICollectionView.prefetchDataSource ב-Android — מאגר תצוגות משותף לשימוש חוזר, הפחתת פעולות inflate.
זיכרון: אחסון RecyclerView.setRecycledViewPool() מפוענח עבור לכל היותר 5 סרטונים שכנים. בגלילה נוספת, שחרור AVPlayerItem במפורש. ללא זה, צפייה ביותר מ-20 Reels גורמת לגדילת זיכרון ל-300–500 MB ו-iOS שולח אזהרת זיכרון. המהנדסים שלנו מוסמכים על ידי אפל וגוגל, ומבטיחים פעולה חלקה אפילו במכשירים עם 2 GB RAM.
| רכיב | מורכבות | זמן משוער |
|---|---|---|
| גלילת עמודים אנכית + נגן משותף | גבוהה | 2–3 ימים |
| טעינה מוקדמת + מטמון | בינונית | 1–2 ימים |
| הקלטה + ריבוי קטעים | גבוהה | 3–4 ימים |
| פילטרים בזמן אמת (Core Image) | גבוהה | 2–3 ימים |
| טקסט + אודיו בייצוא | בינונית | 2 ימים |
| מדד | iOS | Android |
|---|---|---|
| חלקות גלילה (fps) | 60 | 60 |
| השהיית מעבר | <100ms | <120ms |
| שימוש בזיכרון (5 סרטונים) | ~150 MB | ~180 MB |
מה כלול
- תיעוד ארכיטקטורה ו-API.
- קוד מקור עם הערות.
- הגדרת CI/CD וגישה לחנות (App Store Connect, Google Play Console).
- הדרכת צוות הלקוח (2–3 מפגשים).
- 30 ימי תמיכה לאחר השקה.
לוח זמנים ועלות
פיד צפייה עם הפעלה אוטומטית, טעינה מוקדמת וגלילת עמודים — 1–2 שבועות. מחזור מלא (הקלטה, עריכה, פילטרים, פרסום, פיד) — 3–6 שבועות. העלות מחושבת באופן אישי לפי מורכבות האינטגרציה והתכונות הנדרשות. צרו קשר להערכת פרויקט.
מדריך שלב אחר שלב: איך ליישם נגן משותף
- צרו
AVPlayerItem.asset/AVPlayerיחיד ב-Activity/ViewController שלכם. - בכל תא, השתמשו ב-
ExoPlayer/playerLayer.player = player. - ב-
PlayerView.player = player, הקצו את הנגן לתא הנראה כעת. - לטעינה מוקדמת, יישמו prefetchDataSource (iOS) או addMediaItem (Android).
- בדקו על מכשירים עם 2 GB RAM.







