Перейти к основному содержимому

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 entrypointSDK проверяет связанный live/VOD по строковому feedProductId и показывает preview только для доступного контента.
Полный плеерShopStory управляет WebView-плеером, загрузкой, media lifecycle и проверкой origin.
Product handoffТипизированное действие передаётся приложению; нативный router открывает товар по feedProductId.
Follow-meТа же медиасессия продолжается поверх разрешённых экранов без перезапуска.
Cart safetyHost app сообщает текущую поверхность и после смены route применяет решение runtime policy, чтобы скрыть player на cart, checkout, payment, auth и других заблокированных экранах.
Управление запускомRemote policy может отключить SDK-owned surfaces без выпуска новой версии приложения.

Системный Picture-in-Picture и фоновое воспроизведение в этот контракт не входят. Когда приложение уходит в background, воспроизведение ставится на паузу.

Платформенная поставка

КонтурФормат интеграцииЧто получает команда клиента
Native iOSShopStoryPlayerSDK через Swift Package Manager или CocoaPodsДоступ к пакету, точная версия, руководство к этой версии, checklist приёмки и зафиксированная версия для отката.
Flutter on iOSshopstory_player_flutter и совместимая native iOS dependencyЗафиксированная пара версий, Flutter host contract и iOS acceptance checklist.
Другой mobile stackСогласовывается до оценки разработкиОтдельное решение по runtime, ownership и срокам; web-фрагменты не считаются готовым mobile SDK.

Команда клиента получает точную версию пакета и доступ через integration handoff. Access tokens и координаты репозитория публично не размещаются.

Техническая совместимость

КонтурТекущий baseline для оценки
Native iOSiOS 15+, Xcode 16+, Swift Package Manager или CocoaPods. PDP entrypoint и follow-me предоставляются как SwiftUI-компоненты; full-screen player монтируется как UIKit view.
Flutter on iOSFlutter 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 в контракт не входят.

Путь интегратора

  1. Подтвердить mobile stack, поддерживаемую версию ОС и способ подключения пакета.
  2. Зафиксировать поле каталога приложения, равное feedProductId в товарном фиде ShopStory.
  3. Получить applicationId, read-only package access и exact version.
  4. Настроить SDK один раз при запуске приложения без блокировки startup.
  5. Передавать текущую commerce-поверхность из существующего router/state owner и синхронизировать с policy активную playback session после initial route, push, replace, pop и смены tab.
  6. На PDP запрашивать video-state текущего товара и удалять entrypoint при смене товара или закрытии экрана.
  7. Подключить native product route. Cart handler добавляется, только если проекту нужна прямая корзина.
  8. Пройти приёмку 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 и состоянием entrypointTest 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 и rollbackDevice 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 выполняет ещё шесть действий:

  1. Root integration owner создаёт и удерживает одну playback session, один ShopStoryPlayerCoordinator и floating-player controller.
  2. Full-screen container вызывает coordinator.attach(to:), затем coordinator.load(entrypoint:).
  3. Coordinator delegate принимает типизированные product actions и lifecycle events.
  4. При переходе из player в native PDP host отсоединяет player view без teardown, открывает PDP и монтирует ту же сессию в follow-me container.
  5. После каждого route transition host вызывает setCurrentSurface и применяет shouldShowFollowMePlayer() к активной сессии: показывает или скрывает container и синхронизирует playback state.
  6. teardown() вызывается при закрытии, завершении сессии или remote disable. Product handoff между full-screen и follow-me не завершает сессию.

Создавайте ShopStoryPlaybackOwner после успешного configure, когда начинается активная playback session. Root integration owner приложения удерживает этот объект до stop().

player-owner.swift
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 и внутренних имён протокола в медиа нет.

PDP → playerVideo entrypoint появляется для связанного товара и открывает live или запись.
Product handoffПриложение открывает свою PDP, а SDK переносит ту же сессию в follow-me.
Lifecycle policyFollow-me скрывается на cart/checkout и возвращается на разрешённом экране.

Product actions, корзина и остаток

Плеер передаёт намерение открыть товар или добавить его в корзину. Приложение и commerce-система ритейлера проверяют товар и выполняют действие.

  1. Приложение получает типизированное действие и проверяет непустой payload.feedProductId. При отсутствии ID commerce action отклоняется без перехода и изменения корзины.
  2. Native catalogue подтверждает, что товар существует и доступен в нужном регионе или магазине.
  3. Для прямой корзины приложение вызывает свой stock/cart API с текущей сессией покупателя.
  4. Merchant UI считает cart-команду успешной только после подтверждённого изменения корзины.
  5. При отсутствии стабильной 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.

Связанные разделы