Mobile SDK
ShopStory Mobile SDK встраивает product video, полный плеер и продолжение просмотра в приложение ритейлера. Один и тот же сценарий работает для текущего live и опубликованной записи. Каталог, карточка товара, навигация, авторизация, корзина и подтверждение покупки остаются в приложении клиента.
Сейчас SDK подключается к native iOS и Flutter-приложениям на iOS. Для native iOS ShopStory передаёт точную версию SDK; для Flutter — совместимую пару Flutter/native пакетов. Требования к toolchain и версии фиксируются в integration handoff.
На этой странице описаны пользовательский сценарий, ответственность сторон и обязательная приёмка. Доступ к пакетам, команды установки, клиентские routes, applicationId, тестовые товары, rollout и rollback команда интеграции получает отдельно.
Когда выбирать Mobile SDK
Mobile SDK подходит, когда видео должно быть частью нативного покупательского пути:
- точка входа в live или запись находится на карточке товара;
- полный плеер открывается внутри приложения;
- после выбора товара приложение открывает собственную PDP;
- текущая медиасессия продолжается в компактном follow-me player;
- кнопка «Купить» вызывает нативный route или согласованную cart-команду;
- ShopStory не должен получать токены авторизации, профиль покупателя или содержимое корзины.
Для web-сайта используйте Web SDK. Для собственного списка эфиров поверх read-only данных используйте Public API.
Возможности
| Сценарий | Поведение |
|---|---|
| Product entrypoint | SDK проверяет связанный live/VOD по строковому feedProductId и показывает preview только для доступного контента. |
| Полный плеер | ShopStory управляет WebView-плеером, загрузкой, media lifecycle и проверкой origin. |
| Product handoff | Типизированное действие передаётся приложению; нативный router открывает товар по feedProductId. |
| Follow-me | Та же медиасессия продолжается поверх разрешённых экранов без перезапуска. |
| Cart safety | Host app сообщает текущую поверхность и после смены route применяет решение runtime policy, чтобы скрыть player на cart, checkout, payment, auth и других заблокированных экранах. |
| Управление запуском | Remote policy может отключить SDK-owned surfaces без выпуска новой версии приложения. |
Системный Picture-in-Picture и фоновое воспроизведение в этот контракт не входят. Когда приложение уходит в background, воспроизведение ставится на паузу.
Платформенная поставка
| Контур | Формат интеграции | Что получает команда клиента |
|---|---|---|
| Native iOS | ShopStoryPlayerSDK через Swift Package Manager или CocoaPods | Доступ к пакету, точная версия, руководство к этой версии, checklist приёмки и зафиксированная версия для отката. |
| Flutter on iOS | shopstory_player_flutter и совместимая native iOS dependency | Зафиксированная пара версий, Flutter host contract и iOS acceptance checklist. |
| Другой mobile stack | Согласовывается до оценки разработки | Отдельное решение по runtime, ownership и срокам; web-фрагменты не считаются готовым mobile SDK. |
Команда клиента получает точную версию пакета и доступ через integration handoff. Access tokens и координаты репозитория публично не размещаются.
Техническая совместимость
| Контур | Текущий baseline для оценки |
|---|---|
| Native iOS | iOS 15+, Xcode 16+, Swift Package Manager или CocoaPods. PDP entrypoint и follow-me предоставляются как SwiftUI-компоненты; full-screen player монтируется как UIKit view. |
| Flutter on iOS | Flutter 3.24+, Dart 3.5+ и iOS 15+. Flutter adapter и native dependency фиксируются совместимой парой. |
Если native host полностью построен на UIKit, включите в оценку SwiftUI hosting layer для PDP и follow-me. Точные требования выбранной версии пакета из integration handoff имеют приоритет над этим baseline.
Архитектура и ответственность
| ShopStory SDK | Приложение ритейлера |
|---|---|
| Проверяет video-state одного товара | Передаёт стабильный feedProductId из каталога |
| Показывает entrypoint, preview и player | Выбирает место на PDP и нативный full-screen container |
| Валидирует player URL, origin и сообщения WebView | Не создаёт собственный bridge поверх SDK |
| Управляет одной медиасессией и предоставляет policy для follow-me | Сообщает текущую commerce-поверхность и после route transition синхронизирует с policy активную сессию |
| Передаёт типизированное product action | Разрешает товар в своём каталоге и выполняет route/cart command |
| Применяет remote policy и безопасно скрывает UI при ошибке | Выпускает, наблюдает и откатывает зафиксированную версию пакета |
Встраивание и брендинг
SDK работает внутри приложения ритейлера. Приложение сохраняет свои PDP, navigation, cart, auth и native analytics. ShopStory настраивает внешний вид web-плеера для проекта. До приёмки стороны фиксируют, какие элементы можно менять; остальные theme overrides в контракт не входят.
Путь интегратора
- Подтвердить mobile stack, поддерживаемую версию ОС и способ подключения пакета.
- Зафиксировать поле каталога приложения, равное
feedProductIdв товарном фиде ShopStory. - Получить
applicationId, read-only package access и exact version. - Настроить SDK один раз при запуске приложения без блокировки startup.
- Передавать текущую commerce-поверхность из существующего router/state owner и синхронизировать с policy активную playback session после initial route, push, replace, pop и смены tab.
- На PDP запрашивать video-state текущего товара и удалять entrypoint при смене товара или закрытии экрана.
- Подключить native product route. Cart handler добавляется, только если проекту нужна прямая корзина.
- Пройти приёмку live, VOD, no-content, network failure, background, cart и rollback.
Состав работ для оценки
| Work package | Работа приложения | Входные данные |
|---|---|---|
| Dependency | Подключить и зафиксировать SDK; настроить CI-доступ | Exact package version и требования к toolchain |
| Bootstrap | Вызвать configure один раз и сохранить host app независимым от результата | applicationId и test environment |
| Navigation | Сопоставить routes с ShopStorySurface; применить policy после каждого перехода | Route-to-surface map, включая tabs и nested navigation |
| PDP | Передать feedProductId, управлять cancellable lookup и состоянием entrypoint | Test products с live, VOD и no-content |
| Playback | Удерживать одну session/coordinator, full-screen container и follow-me container | Выбранная UIKit/SwiftUI topology |
| Product action | Проверить ID и открыть native PDP; при необходимости вызвать cart-команду | Product route и отдельное решение по direct cart |
| Release | Прогнать Release build, physical-device acceptance и rollback | Device matrix, checklist и предыдущая принятая версия |
Оценка direct cart требует контракта merchant API и fallback. Оценка базового сценария с открытием native PDP от cart API не зависит.
Идентификатор товара
feedProductId связывает товарный фид, ShopStory и каталог приложения. Храните его строкой: ведущие нули и буквенные символы могут быть значимыми.
merchant catalogue product
│
├── feedProductId в товарном фиде
├── feedProductId на native PDP
└── feedProductId в product action из player
Не используйте внутренний ShopStory productId для нативной маршрутизации. URL из product action является справочным полем и не должен управлять переходом внутри приложения.
Минимальный host contract
Ниже показан минимальный host contract. Сверяйте сигнатуры с документацией к установленной версии пакета.
Native iOS
import ShopStoryPlayerSDK
private let shopStory = ShopStorySDK.shared
private var shopStoryConfigured = false
private var productStateRequest: ShopStoryProductStreamStateRequest?
func configureShopStory() {
do {
try shopStory.configure(
applicationId: applicationId,
hostAppVersion: Bundle.main.object(
forInfoDictionaryKey: "CFBundleShortVersionString"
) as? String
)
shopStoryConfigured = true
} catch {
shopStoryConfigured = false
hideShopStoryEntrypoint()
reportIntegrationError(error)
}
}
func loadShopStory(for product: Product) {
guard shopStoryConfigured else {
hideShopStoryEntrypoint()
return
}
shopStory.setCurrentSurface(.productDetail)
productStateRequest?.cancel()
productStateRequest = shopStory.loadProductStreamState(
feedProductId: product.feedProductId
) { result in
switch result {
case let .success(.active(entrypoint)):
showShopStoryEntrypoint(entrypoint)
case .success(.unavailable):
hideShopStoryEntrypoint()
case let .failure(error):
hideShopStoryEntrypoint()
reportIntegrationError(error)
}
}
}
Вызывайте конфигурацию один раз при запуске приложения. Ошибка оставляет ShopStory UI скрытым и не прерывает запуск host app. При смене PDP отменяйте предыдущий request. .unavailable означает отсутствие доступного видео; .failure(error) передавайте в диагностический logger приложения. Полученный entrypoint передавайте в coordinator целиком; не собирайте player URL вручную.
Full-screen player и follow-me в native iOS
Lookup из предыдущего примера только получает entrypoint. Для воспроизведения host app выполняет ещё шесть действий:
- Root integration owner создаёт и удерживает одну playback session, один
ShopStoryPlayerCoordinatorи floating-player controller. - Full-screen container вызывает
coordinator.attach(to:), затемcoordinator.load(entrypoint:). - Coordinator delegate принимает типизированные product actions и lifecycle events.
- При переходе из player в native PDP host отсоединяет player view без teardown, открывает PDP и монтирует ту же сессию в follow-me container.
- После каждого route transition host вызывает
setCurrentSurfaceи применяетshouldShowFollowMePlayer()к активной сессии: показывает или скрывает container и синхронизирует playback state. teardown()вызывается при закрытии, завершении сессии или remote disable. Product handoff между full-screen и follow-me не завершает сессию.
Создавайте ShopStoryPlaybackOwner после успешного configure, когда начинается активная playback session. Root integration owner приложения удерживает этот объект до stop().
import ShopStoryPlayerSDK
import UIKit
@MainActor
final class ShopStoryPlaybackOwner: ShopStoryPlayerCoordinatorDelegate {
private let sdk = ShopStorySDK.shared
private let floatingController = ShopStorySDK.shared.makeFloatingPlayerController()
private(set) lazy var coordinator = ShopStoryPlayerCoordinator(delegate: self)
func start(entrypoint: ShopStoryProductDetailEntrypoint, in container: UIView) {
coordinator.attach(to: container)
coordinator.load(entrypoint: entrypoint)
}
func shopStoryPlayerCoordinator(
_ coordinator: ShopStoryPlayerCoordinator,
didReceive action: ShopStoryProductAction
) {
guard let feedProductId = action.payload.feedProductId,
!feedProductId.isEmpty else {
reportInvalidProductAction(action)
return
}
handleProductAction(action.kind, feedProductId: feedProductId)
}
func reconcile(
surface: ShopStorySurface,
mediaOrientation: ShopStoryMediaOrientation
) {
sdk.setCurrentSurface(surface)
if sdk.shouldShowFollowMePlayer() {
let geometry = sdk.followMeGeometry(mediaOrientation: mediaOrientation)
floatingController.show(using: geometry)
coordinator.resumePlayback()
} else {
coordinator.pausePlayback(reason: .surfaceBlocked)
floatingController.suppress()
}
}
func shopStoryPlayerCoordinator(
_ coordinator: ShopStoryPlayerCoordinator,
didUpdate event: ShopStoryPlayerLifecycleEvent
) {
handlePlayerLifecycle(event)
}
func stop() {
floatingController.close()
coordinator.teardown()
}
}
handleProductAction, handlePlayerLifecycle и native containers принадлежат приложению. Versioned guide из integration handoff содержит полную схему удержания session, detach, follow-me mount и release.
Flutter on iOS
final shopStory = ShopStoryPlayerFlutter.instance;
var shopStoryConfigured = false;
if (shopStory.isSupportedPlatform) {
try {
await shopStory.configure(applicationId: applicationId);
shopStoryConfigured = true;
} catch (error, stackTrace) {
FlutterError.reportError(
FlutterErrorDetails(exception: error, stack: stackTrace),
);
}
}
final observer = ShopStoryNavigatorObserver(
surfaceForRoute: mapRouteToShopStorySurface,
);
MaterialApp(
navigatorObservers: [observer],
home: const AppRoot(),
);
final productScreen = shopStoryConfigured
? ShopStoryProductDetailScope(
feedProductId: product.feedProductId,
child: ProductDetailView(product: product),
)
: ProductDetailView(product: product);
Один root Navigator должен использовать observer. Для declarative router, tabs и nested navigator передавайте текущую поверхность из существующего state owner через setCurrentSurface.
До открытия ShopStory UI зарегистрируйте product-action handlers в integration root. onOpenProduct возвращает результат после принятого native route. onAddProduct нужен только для прямой корзины и возвращает успех после подтверждённой cart-команды. Регистрацию храните до teardown и затем вызовите dispose().
Пользовательский сценарий
Короткие ролики показывают поведение интеграции на нейтральных данных. Они воспроизводятся без звука; каждый сценарий описан под роликом. Клиентских данных, application IDs и внутренних имён протокола в медиа нет.
Product actions, корзина и остаток
Плеер передаёт намерение открыть товар или добавить его в корзину. Приложение и commerce-система ритейлера проверяют товар и выполняют действие.
- Приложение получает типизированное действие и проверяет непустой
payload.feedProductId. При отсутствии ID commerce action отклоняется без перехода и изменения корзины. - Native catalogue подтверждает, что товар существует и доступен в нужном регионе или магазине.
- Для прямой корзины приложение вызывает свой stock/cart API с текущей сессией покупателя.
- Merchant UI считает cart-команду успешной только после подтверждённого изменения корзины.
- При отсутствии стабильной cart-команды действие открывает нативную PDP.
В native iOS действие приходит через delegate без completion. Flutter handler возвращает адаптеру типизированный результат для host-side lifecycle, но этот результат не передаётся обратно в embedded player как подтверждение корзины. Поэтому состояние кнопки в player и аналитическое событие не доказывают изменение корзины.
По умолчанию действие открывает нативную PDP. Прямую корзину включайте только по контракту из документации к поставленной версии. Подтверждённый результат добавления показывает интерфейс ритейлера. Плеер ShopStory не хранит состояние корзины.
Проверка остатка внутри cart-команды приложения входит в native cart flow. Кастомная commerce-интеграция нужна, если ShopStory должен вызвать отдельный stock API или учесть магазин, регион либо способ получения до cart-команды. Проверка выполняется в момент действия пользователя: между синхронизациями данные фида могут устареть.
Lifecycle и безопасное поведение
- SDK сохраняет одну playback session при переходе full-screen → native PDP → follow-me.
- Приложение сообщает реальную видимую поверхность после push, replace, pop и изменения tab, затем синхронизирует активную сессию с SDK policy.
- Неизвестная поверхность блокируется до явного разрешения.
- Cart, checkout, payment, auth и profile исключаются из follow-me по runtime policy.
- При background media ставится на паузу; отдельный background audio contract отсутствует.
- При закрытии или отключении policy container удаляется, player teardown завершается, handlers освобождаются.
- Ошибка SDK оставляет PDP, navigation, cart и checkout приложения рабочими.
unavailable — штатное состояние: для товара нет доступного live/VOD, поэтому entrypoint не показывается. При технической ошибке приложение записывает typed error в свой диагностический logger и удаляет пустой блок с PDP.
Security и privacy boundary
SDK владеет WebView-конфигурацией и проверяет HTTPS, exact origin, main-frame navigation, схему и размер сообщения. Не создавайте параллельный WKWebView, JavaScript bridge или URL-конструктор по фрагментам этой страницы.
Native SDK использует только данные, необходимые для video integration:
applicationIdпроекта;- platform, SDK version и host app version;
- текущий
feedProductId; - player/preview URLs из ответа ShopStory;
- runtime policy и локальные lifecycle diagnostics.
Не передавайте в SDK access tokens, session cookies, платёжные данные, профиль, точную геопозицию, advertising ID или содержимое корзины. Web-плеер отправляет собственные события аналитики ShopStory; приложение ритейлера отвечает за аналитику нативной навигации и подтверждённого результата в своём commerce-интерфейсе.
Подробнее: Безопасность интеграции.
Приёмка перед выпуском
- Exact package version зафиксирована в lockfile; для Flutter там же зафиксирована совместимая native dependency.
feedProductIdсовпадает в mobile catalogue и товарном фиде, включая ведущие нули.- Для товара с видео entrypoint открывает live и VOD; для товара без видео UI отсутствует без пустого места.
- Смена товара отменяет предыдущий lookup и не показывает устаревший preview.
- Product action открывает правильную native PDP.
- Для прямой корзины merchant UI показывает success только после подтверждения cart API; состояние кнопки в player не используется как подтверждение.
- Follow-me сохраняет ту же сессию, соблюдает safe area и скрывается на cart/checkout/payment/auth/profile.
- Background ставит media на паузу; close и remote disable полностью освобождают player UI.
- Потеря сети и некорректная конфигурация не блокируют PDP, navigation или checkout.
- Проверены accessibility labels, Dynamic Type/масштаб текста и целевые orientation.
- На поддерживаемом физическом iPhone проверены live, VOD, звук, background/foreground, product handoff и follow-me.
- Release build и rollback на предыдущую принятую exact version проверены до rollout.
Integration handoff
Команда ShopStory передаёт назначенным инженерам клиента:
- package repository access без секретов в исходном коде;
- exact immutable version; для Flutter — совместимую пару Flutter/native зависимостей;
- requirements конкретной версии;
applicationId, test products и ожидаемые live/VOD states;- route-to-surface map и выбранный cart contract;
- acceptance checklist, rollout, rollback и канал поддержки.
При обращении в поддержку приложите SDK version, Xcode/Flutter/iOS version, host app version, applicationId, затронутый feedProductId, время проблемы и типизированную ошибку. Не отправляйте auth tokens, session cookies или полные production logs.