Titik masuk SDK iOS HappyOyster adalah singleton tingkat proses HappyOysterEngine.shared. Metode bisnis semuanya bersifat async throws, melempar OysterSDKError saat gagal. Satu pengalaman dibawa oleh handle OysterTravel, yang mencakup tiga mode — adventure, directing, dan acting — pada UIKit maupun SwiftUI.
Catatan
- Dokumen ini adalah deskripsi fitur eksternal + referensi antarmuka untuk Happy Oyster iOS SDK: dokumen ini menjelaskan parameter, waktu, penggunaan, dan contoh singkat dari setiap tipe dan metode publik, satu per satu.
- Untuk alur integrasi lengkap (pengaturan proyek, konfigurasi dependensi, koordinasi sisi server, pelaksanaan end-to-end), lihat dokumentasi proyek sampel; tidak diulang di sini.
1. Konsep Inti
Beberapa istilah terlebih dahulu, agar sisanya lebih mudah dibaca.
Konsep | Deskripsi |
|---|---|
token | Token autentikasi HTTP (API Key sementara Bailian). Server Anda menukarnya melalui API Bailian dan mengirimkannya ke bawah; diinjeksikan melalui |
ticket | Kredensial uji coba sekali pakai. Server Anda menukarnya melalui platform terbuka |
World | World AI, berisi karakter dan adegan. Dibuat dan dikelola oleh server Anda; SDK tidak terlibat. |
Travel | Pengalaman real-time tunggal, sesuai dengan satu handle |
Status sesi |
|
Mode |
|
2. Ikhtisar
Setelah mengintegrasikan SDK ini, aplikasi Anda dapat memasuki "world" yang dihasilkan secara real-time oleh AI, dan memiliki pengalaman video interaktif real-time dalam tiga mode — adventure, directing, atau acting: mulai pengalaman → pemutaran real-time → interaksi real-time → kontrol proses (pause/resume/rewind/end) → callback status dan error.
Setiap mode mendukung serangkaian kapabilitas yang berbeda. Gerakkan UI Anda dari mode yang dikembalikan oleh start():
Kapabilitas |
|
|
|
|---|---|---|---|
| Ya | Tidak | Tidak |
| Tidak | Ya | Ya |
| Tidak | Ya | Ya |
| Tidak | Ya | Tidak — sembunyikan titik masuk |
| Ya | Ya | Ya |
| Diterapkan | Diabaikan | Diabaikan |
CatatanWorld acting mengutamakan potret: start() mengembalikan aspectRatio (9:16 / 16:9) untuk sesi tersebut. Gunakan ini untuk memilih orientasi pemutar dan ukuran kontainer sebelum menarik aliran (lihat §8).
import HappyOysterSDK; titik masuk intinya terdiri dari dua tipe.
HappyOysterEngine — titik masuk orkestrasi, singleton tingkat proses HappyOysterEngine.shared.
Method / Property | Deskripsi |
|---|---|
| Inisialisasi runtime dan daftarkan mesin real-time secara otomatis (sekali, sebelum |
| Injeksikan / perbarui token autentikasi HTTP |
| Buat handle sesi dengan kredensial sekali pakai |
| Lepaskan sumber daya (dapat |
| Versi SDK saat ini |
OysterTravel — handle sesi untuk satu pengalaman, dibuat oleh createTravel(ticket:).
Method / Property | Deskripsi |
|---|---|
| Tampilan pemutaran (UIKit / SwiftUI) |
| Aliran push perubahan status + error / status saat ini yang dapat diobservasi |
| Hubungkan dan mainkan (opsional meminta durasi maksimum pengalaman adventure) |
| Instruksi teks mode Directing |
| Kontrol mode Adventure |
| Jeda / lanjutkan ( |
| Putar mundur (hanya saat dijeda) |
| Akhiri (idempoten, pastikan untuk memanggilnya) |
| Lepaskan / pulihkan mikrofon sementara |
Ada juga beberapa helper exports: OysterLog untuk mengambil alih pencatatan internal SDK; HappyOysterEngine.version untuk membaca versi; OysterVideoView adalah tampilan pemutaran SwiftUI (setara dengan videoView). Penggunaan dijelaskan selanjutnya.
Siklus hidup: inisialisasi → injeksi token → buat sesi → mount video + berlangganan event → mulai pemutaran → interaksi → akhiri. SDK tidak bertanggung jawab untuk membuat atau mengelola world (dilakukan oleh server Anda), dan tidak mengekspos detail komunikasi real-time tingkat rendah.
3. Quick Start
Alur lengkap dari inisialisasi hingga pengakhiran, dengan komentar langkah demi langkah. Detail setiap API ada di §6.
import HappyOysterSDK
// 1) Initialize (as early as possible after app launch, once before createTravel)
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
apiHost: "[workspace-id].[region].maas.aliyuncs.com",// For the API host, refer to the Bailian documentation https://www.alibabacloud.com/help/en/model-studio/base-url
model: "happyoyster-1.0-adventure" // Versioned model name enabled for your account, required; see the Happy Oyster model documentation
))
// 2) (Optional) Take over logging: set level + custom output
OysterLog.setMinimumLevel(.info)
OysterLog.setHandler { level, tag, message in
print("[Oyster][\(level)][\(tag)] \(message)")
}
// 3) Inject the HTTP auth token (Bailian temporary API Key, delivered by your server)
engine.updateToken(temporaryApiKey)
// 4) Create a session handle with a one-time ticket (not yet connected)
let travel = try engine.createTravel(ticket: ticket)
Task { @MainActor in
// 5) Mount the video (UIKit; for SwiftUI use OysterVideoView(travel:))
containerView.addSubview(travel.videoView)
// 6) Subscribe to events before start, to avoid missing early statuses
let eventTask = Task {
for await event in travel.events {
switch event {
case .statusChanged(let status): render(status) // §7 status table
case .error(let error): handle(error) // §9 error.code / error.kind
}
}
}
do {
// 7) Connect and play
let data = try await travel.start()
// 8) Interact based on mode
if data.mode == .directing {
_ = try await travel.sendInstruct(content: "Suddenly it starts to pour")
} else {
travel.sendCommand(OysterAdventureCommand(translation: .front))
}
} catch let error as OysterSDKError {
handle(error)
}
// 9) Funnel every exit path into a single end()
_ = try? await travel.end()
eventTask.cancel()
}
// Exit the SDK / switch gateway: await engine.cleanup()
CatatanUntuk pengaturan proyek, konfigurasi dependensi (termasuk adaptor AliRTC dan biner vendor yang diperlukan), koordinasi sisi server, dan pelaksanaan end-to-end, lihat dokumentasi proyek sampel.
4. Persyaratan
Item | Requirement |
|---|---|
OS Minimum | iOS 15.0+ (semua tipe publik ditandai dengan |
Language | Swift ( |
Konkurensi | Akses main-thread (tipe entri ditandai dengan |
Network | Akses jaringan publik diperlukan |
Izin |
|
Izin mikrofon: Pengalaman video interaktif real-time memerlukan saluran audio/video real-time dua arah (uplink + downlink), sehingga SDK menggunakan mikrofon lokal saat berjalan — ini bukan perekaman. Info.plist harus menyediakan NSMicrophoneUsageDescription, jika tidak, memulai penangkapan real-time akan crash. Saat Anda memerlukan akses mikrofon eksklusif (misalnya pengenalan suara), gunakan pauseLocalAudioCapture() / resumeLocalAudioCapture() untuk melepaskannya dan memulihkannya sementara waktu (lihat §6.2).
5. Integrasi dan Autentikasi
5.1 Integrasi dan Dependensi
import HappyOysterSDK adalah semua yang Anda butuhkan di tingkat kode. SDK didistribusikan sebagai biner yang telah dikompilasi sebelumnya (xcframework) melalui subspecs CocoaPods, dipublikasikan ke CocoaPods Trunk publik — deklarasikan dependensi di Podfile Anda:
# HappyOysterSDK / AliVCSDK_ARTC are both published on the public CocoaPods source.
pod 'HappyOysterSDK', '1.0.3' # Aggregate entry point (Core + World)
pod 'HappyOysterSDK/UI', '1.0.3' # Optional default UI components (video view, control HUD)
pod 'HappyOysterSDK/StreamAliRTC', '1.0.3' # Video stream + AliRTC engine adapter (already depends on Stream)
# RTC vendor binary: weak-linked by the SDK, not redistributed with it — bring your own.
pod 'AliVCSDK_ARTC', '7.11.0'
CatatanAliVCSDK_ARTC diperlukan setiap kali Anda menarik HappyOysterSDK/StreamAliRTC: jika tidak ada, SDK secara diam-diam kembali ke Loopback — SDK dapat terhubung dan mencapai running, tetapi menampilkan layar hitam tanpa error.
Pengaturan proyek dan alur inisialisasi end-to-end ditangani oleh proyek sampel; lihat dokumentasi proyek sampel. Dokumen ini berfokus pada antarmuka itu sendiri.
5.2 Model Autentikasi
SDK tidak memperoleh atau menyegarkan token, menjaga agar tetap ringan. Autentikasi memiliki dua lapisan, dan integrator mengelola siklus hidupnya:
- Token autentikasi HTTP (API Key sementara Bailian): Server Anda menukarnya melalui API Bailian dan mengirimkannya ke bawah; diinjeksikan melalui
updateToken(_:). Beberapa layanan internal SDK memanggil gateway Bailian secara langsung, membawa token ini untuk autentikasi — oleh karena itu harus berupa API Key sementara yang dikeluarkan oleh Bailian, bukan token dari layanan bisnis Anda sendiri. SDK hanya menyimpan yang terbaru, tidak menyimpan atau menyegarkannya; setelah kedaluwarsa, Anda menukar dan menginjeksikannya kembali. - Kredensial uji coba sekali pakai
ticket: Server Anda menukarnya melalui platform terbukaget-travel-credential(prefikstk_, berlaku selama 30 menit, sekali pakai), digunakan sebagai argumencreateTravel(ticket:); menjadi tidak valid setelah pengalaman berakhir (secara normal atau tidak normal) atau kedaluwarsa, dan tidak dapat digunakan kembali.
CatatanAK, kunci penandatanganan, dan kredensial hak istimewa tinggi lainnya hanya ada di server Anda; SDK klien tidak pernah menyentuhnya. Apa yang diterima klien selalu berupa API Key sementara yang berumur pendek.
CatatanFeature Gate (sakelar jarak jauh / peningkatan paksa): Server dapat menonaktifkan SDK dari jarak jauh atau menetapkan versi minimum yang didukung. Saat dinonaktifkan, panggilan yang dibatasi (start / pause / resume / rewind / sendInstruct / sendCommand) ditolak dengan 108001 dan pengalaman yang sedang berjalan dihentikan oleh SDK (lihat §9). OysterSDKError.raw membawa alasan yang dapat dibaca manusia; minta pengguna untuk melakukan upgrade saat versinya terlalu rendah.
Menangani kedaluwarsa token: Setelah token autentikasi HTTP di atas kedaluwarsa, segera minta token baru dari server Anda dan injeksikan melalui updateToken(_:). Ada dua tempat di mana Anda perlu menentukan apakah token telah kedaluwarsa:
- Saat memanggil API seperti
engine.createTravelatautravel.start, tangani error dan periksa tipe error token kedaluwarsa/tidak valid (101001/101002); setelah menyuntikkan token baru, panggil ulang API yang sesuai. - Saat mendengarkan event
.errordariOysterTravelEvent, periksa tipe error token kedaluwarsa/tidak valid, minta ulang token, dan suntikkan.
6. Referensi API
Ada dua tipe entri, keduanya @MainActor dan @available(iOS 15.0, *). Metode bisnis bersifat async throws dan melempar OysterSDKError saat gagal; metode dengan nilai kembalian semuanya ditandai dengan @discardableResult.
6.1 HappyOysterEngine
Singleton tingkat proses, titik masuk orkestrasi. init tidak bersifat publik — selalu gunakan HappyOysterEngine.shared; jangan menginstansiasinya sendiri (mesin real-time yang mendasarinya juga merupakan singleton proses).
@MainActor @available(iOS 15.0, *)
public final class HappyOysterEngine {
public static let shared: HappyOysterEngine // the unique process-level instance
public static let version: String // current SDK version
@discardableResult
public func initialize(config: OysterConfig) -> Bool // initialize (once before createTravel)
public func updateToken(_ token: String) // inject/update the HTTP auth token
public func createTravel(ticket: String) throws -> OysterTravel // create a session handle with a one-time credential
public func cleanup() async // release resources (can initialize again)
}
initialize(config:)
- Tujuan: Menginisialisasi runtime dan mendaftarkan mesin real-time secara otomatis (tidak perlu pendaftaran manual oleh host).
- Parameter:
config.apiHostadalah URL gateway Bailian, bukan server bisnis Anda, dan wajib diisi; di lingkungan pra-rilis/ujicoba, Anda harus secara eksplisit meneruskan gateway yang sesuai, jika tidak, permintaan akan gagal (misalnya106001, domain tidak dapat diselesaikan).config.modeladalah nama model berversi yang diaktifkan untuk akun Anda dan juga wajib diisi tanpa nilai default — Happy Oyster dibagi menjadi sub-model per-mode, sehingga SDK tidak dapat menyimpulkan mana yang harus digunakan. Keduanya harus termasuk dalam akun dan wilayah yang sama dengan token yang diinjeksi. Bidang lainnya ada di §8OysterConfig. - Satu model melayani satu
mode: setiap model per-mode memiliki rute gateway-nya sendiri, sehingga satuinitializehanya melayani dunia dengan satumodetersebut. Jika aplikasi Anda menawarkan dunia dalam beberapa mode, cukup panggilinitialize()lagi dengan model yang sesuai sebelum memasuki dunia dengan mode yang berbeda — saat idle, konfigurasi terbaru yang akan digunakan, tidak perlucleanup()dan token yang diinjeksi tetap dipertahankan; saat Travel sedang berlangsung, panggilan akan diabaikan, jadiend()terlebih dahulu. Model yang tidak sesuai denganmodedunia akan ditolak oleh gateway denganAccessDenied, dinormalisasi menjadi106003. - Kapan digunakan: Panggil sekali sebelum
createTravel, sedini mungkin setelah peluncuran aplikasi. - Mengembalikan:
Bool— apakahconfigyang Anda berikan berlaku. Bernilaifalsedalam dua kasus: konfigurasi tidak valid (apiHost/modelkosong atau tidak membentuk URL gateway yang valid), atau Travel sedang berjalan sehingga panggilan diabaikan. Dalam kedua kasus tersebut, runtime dibiarkan tidak berubah. - Catatan: Saat idle, memanggilnya kembali akan mengonfigurasi ulang runtime dengan konfigurasi baru (mengubah
apiHost/modeltidak memerlukancleanup(), dan token yang diinjeksi tetap dipertahankan); ini menjadi no-op dengan peringatan hanya saat Travel sedang berlangsung, jadiend()terlebih dahulu. Konfigurasi yang tidak valid membiarkan runtime tidak berubah. Jangan gunakanisReadyuntuk mengetahui apakah re-initializeberhasil — jika ditolak, konfigurasi sebelumnya masih berlaku danisReadytetaptrue;isReadymenjawab "apakah engine dapat digunakan sekarang", nilai pengembalian menjawab "apakah konfigurasi yang baru saja saya berikan telah berlaku".
OysterLog
- Tujuan: Mengonfigurasi dan mengambil alih pencatatan internal SDK, mencetaknya ke dalam modul pencatatan Anda sendiri. Menyediakan
setMinimumLevel(_:)untuk mengatur level dansetHandler(_:)untuk output kustom (lihat contoh §3).
updateToken(_:)
- Tujuan: Menyuntikkan / memperbarui token autentikasi HTTP (API Key sementara Bailian, §5.2).
- Kapan digunakan: Setelah
initialize, dapat dipanggil kapan saja; tukar ulang dan panggil lagi setelah token kedaluwarsa atau setelah menerima error terkait autentikasi (101001/101002). - Catatan: Tidak ada operasi dan menampilkan peringatan jika belum di-
initialize.
createTravel(ticket:)
- Tujuan: Membuat handle sesi tunggal dengan
ticketsekali pakai. - Parameter:
ticketadalah kredensial sekali pakai; setelah dibuat, dianggap terpakai untuk pengalaman ini. - Kapan digunakan: Panggil sebelum setiap pengalaman baru; handle yang dikembalikan belum terhubung dan Anda harus memanggil
travel.start()setelahnya. Video diambil dari handle yang dikembalikan (lihat §6.2). - Catatan (
throwssinkron): melempar100001jika belum di-initialize; melempar103004jika dipanggil lagi sebelum Travel sebelumnya di-end()(setiap mesin hanya mengizinkan satu Travel aktif pada satu waktu).
cleanup()
- Tujuan: Melepaskan sumber daya SDK (mengakhiri travel aktif, konfigurasi runtime, token).
- Kapan digunakan: Saat keluar sepenuhnya dari SDK atau perlu mengubah
config. - Catatan:
async— secara internal ia secara deterministik mengakhiri travel aktif saat ini denganend()terlebih dahulu, kemudian meruntuhkan runtime, tanpa meninggalkan fire-and-forget. Setelah dilepaskan, Anda dapat melakukaninitializelagi.
6.2 OysterTravel
Handle sesi yang dibuat oleh createTravel; sekali pakai, menjadi tidak valid setelah status terminal (end / akhir sisi server / kegagalan) tercapai, memerlukan createTravel baru melalui engine. Ini juga merupakan ObservableObject (@Published status, dapat langsung menggerakkan SwiftUI).
@MainActor @available(iOS 15.0, *)
public final class OysterTravel: ObservableObject {
@Published public private(set) var status: OysterTravelStatus // current external status (observable)
public var isEnded: Bool { get } // whether a terminal state is reached (synchronously readable)
public var videoView: UIView { get } // UIKit playback view; for SwiftUI use OysterVideoView(travel:)
public var events: AsyncStream<OysterTravelEvent> { get } // status change + error, multi-subscribe
@discardableResult public func start() async throws -> OysterStartTravelData // connect and play
@discardableResult public func start(maxExperienceTimeSec: Int?) async throws -> OysterStartTravelData // connect and play + request max experience duration (adventure mode only)
@discardableResult public func pause() async throws -> OysterTravelStateData // pause (directing / acting)
@discardableResult public func resume() async throws -> OysterTravelStateData // resume
@discardableResult public func rewind(toSec: TimeInterval) async throws -> OysterRewindTravelData // rewind (paused only)
@discardableResult public func end() async throws -> OysterEndTravelData // end (idempotent, be sure to call)
@discardableResult public func sendInstruct(content: String) async throws -> OysterSendInstructData // directing-mode text instruction
public func sendCommand(_ command: OysterAdventureCommand) // adventure-mode control (fire-and-forget)
public func flushCommands() // manually send an already-submitted pending command
public func pauseLocalAudioCapture() async // temporarily yield the microphone
public func resumeLocalAudioCapture() async // restore microphone occupation
}
Tampilan video videoView / OysterVideoView(travel:)
- Tujuan: Entri rendering untuk gambar jarak jauh — "SDK menyediakan tampilan, host menempatkannya". Untuk UIKit, ambil
travel.videoView; untuk SwiftUI, gunakanOysterVideoView(travel:). - Kapan digunakan: Tersedia segera setelah handle dibuat (akses berulang mengembalikan tampilan yang sama); mount ke dalam hierarki apa pun, dan setelah engine siap, tampilan akan dirender secara otomatis. Mount sebelum atau setelah
start()keduanya berfungsi, tanpa layar hitam. - Catatan: Saat sesi berakhir, SDK secara otomatis melepaskan binding rendering; hapus tampilan dari hierarki sesuai kebutuhan.
Event dan status events / status / isEnded
- Tujuan:
eventsadalah aliran push perubahan status + error;statusadalah status eksternal saat ini yang dapat diobservasi;isEndedadalah flag yang dapat dibaca secara sinkron untuk status terminal. - Kapan digunakan: Disarankan untuk mulai mengonsumsi
eventssebelumstart(), untuk menghindari terlewatnya status awal. - Catatan: Setiap akses ke
eventsmengembalikan stream independen, mendukung multi-subscription; berhenti berlangganan = mengakhiri iterasifor await(atau menghancurkanTaskyang ditahan). Di SwiftUI Anda dapat mengamatistatussecara langsung dengan@StateObject/@ObservedObject(error tetap masuk melaluievents). Lihat §7.
start() / start(maxExperienceTimeSec:)
- Tujuan: Gunakan
ticketyang diambil pada saat pembuatan untuk ditukar dengan konfigurasi travel + join RTC dan terhubung untuk bermain. Jika berhasil, SDK secara otomatis membuat koneksi real-time dan memulai polling status internal, memunculkan status melaluievents. - Parameter:
maxExperienceTimeSec(opsional) — meminta durasi maksimum (dalam detik) untuk pengalaman petualangan ini. Nilai dikirim ke server apa adanya; nilai yang diizinkan, durasi efektif aktual, dan waktu berakhir otomatis semuanya ditentukan oleh server — SDK tidak melakukan validasi lokal. Meneruskannil(atau memanggilstart()tanpa parameter) menggunakan durasi default server. Mode Directing mengabaikan parameter ini. Ketika waktu habis, server mengakhiri pengalaman dan host menerima status terminalendedmelaluievents(sama seperti akhir sisi server, lihat §7). - Mengembalikan:
OysterStartTravelData(mode/version/encryptedTravelId, dll., lihat §8), yang menentukan UI interaksi. - Error:
401010/401011(kredensial tidak valid/terpakai),403002(world belum siap),403007(spesifikasi layanan tidak diaktifkan, misal acting),429001/429002(batas konkurensi / kapasitas habis),500001(kegagalan resource/server),103004(start konkuren). modeduniaticketharus cocok denganmodelyang diteruskan keinitialize(tanggung jawab pemanggil): dengan model per-mode, setiap model memiliki rute gateway-nya sendiri, danstart()mengirimticketke rute model yang saat ini diinisialisasi. SDK tidak dan tidak dapat memverifikasi ini untuk Anda sebelumnya —modedikirimkan oleh respons dari panggilanstart()ini sendiri (OysterStartTravelData.mode); sebelum panggilan, SDK hanya menyimpanticketyang buram dan nama model, tanpa mode untuk dibandingkan, dan menyimpulkan mode dari nama model berarti menebak skema penamaan milik server, yang tidak dilakukan oleh SDK. Jadi: sebelum memasuki dunia dengan mode yang berbeda, panggilinitialize()lagi dengan model yang sesuai (saat idle, konfigurasi terbaru yang digunakan — tidak perlucleanup()dan token dipertahankan; saat Travel sedang berlangsung, panggilan diabaikan, jadiend()terlebih dahulu). Jika tidak cocok,start()ini akan gagal di gateway; saat mendiagnosis, pertama-tama periksa apakahmodelsaat ini danmodedari duniaticketsaling terkait, lalu lihat kode kesalahan kredensial di atas.- Catatan (auto-end saat tidak ada stream): Server mengirimkan "no-stream timeout" (default ~30s). Jika, setelah terhubung, tidak ada stream yang diterima dalam durasi tersebut (tidak pernah mencapai
running), SDK secara otomatis mengakhiri pengalaman, beralih kefailed, dan memunculkan105006melalui.errordarievents(fatal; tangani dengan kembali ke layar pra-start, tidak perlu menghitung waktunya sendiri).
pause() / resume()
- Tujuan: Menjeda / melanjutkan pengalaman (idempoten).
- Kapan digunakan: Didukung oleh world
directingdanacting; tidak didukung olehadventure. Gunakanmodeyang dikembalikan olehstart()untuk memutuskan terlebih dahulu apakah akan menampilkan tombol pause.pausemengharuskan status saat ini beruparunning;resumemengharuskanpaused. - Error:
103001(tidak ada pengalaman aktif),103002(status/versi tidak diizinkan),103003(ketidakcocokan mode).
rewind(toSec:)
- Tujuan: Memutar mundur ke detik yang ditentukan. Jika berhasil, SDK secara otomatis bergabung kembali dengan rtcConfig asli dan kembali ke pemutaran.
- Kapan digunakan: Hanya dapat dimulai dalam status
paused, dan hanya oleh worlddirecting—actingdanadventuretidak mendukung putar mundur, jadi sembunyikan titik masuk putar mundur dalam mode tersebut dan jangan memanggilnya. - Error:
103001,103002.
end()
- Tujuan: Mengakhiri pengalaman (idempoten, dapat dipanggil berulang kali). Setelah panggilan berhasil atau keluar secara tidak normal, SDK secara otomatis memutuskan koneksi real-time, menghentikan polling, dan melepaskan semua resource sesi;
ticketmenjadi tidak valid pada saat yang sama, dan handle memasuki status terminal. - Kapan digunakan / Catatan: Baik pengguna keluar secara aktif maupun pengalaman berakhir secara pasif (kedaluwarsa timer, menerima
.ended/.failed, penghancuran halaman), pastikan satuend()tercapai, jika tidak, resource jarak jauh mungkin tidak dilepaskan tepat waktu. Disarankan untuk menyalurkan semua jalur keluar ke metode pembersihan idempoten yang sama.
sendInstruct(content:) (mode directing)
- Tujuan: Mengirim instruksi teks untuk menggerakkan alur cerita.
- Kapan digunakan: Mode directing; dikirim langsung saat
running, di-cache saatpauseddan dikirim ulang dengan frame pertama setelah dilanjutkan kembali kerunningmelalui reconnect. - Error:
103001,103002,103003(dipanggil dalam mode adventure),403004(moderasi konten),404000(travel tidak ditemukan).
sendCommand(_:) / flushCommands() (mode adventure)
sendCommand: Kirim perintah kontrol direction/view/action (lihat §8OysterAdventureCommand; fire-and-forget, tidak ada pengembalian, tidak melempar error). Efektif hanya dalam mode adventure saatrunning. Input eksternal dapat berfrekuensi tinggi setiap frame, dan SDK melakukan throttle secara internal (sampling latest-wins, penggabungan frame pada line rate RTC); host tidak perlu melakukan throttle sendiri. Perhatikan bahwa respons server terhadap perintah itu sendiri memiliki latensi, sehingga waktu efektif sebenarnya tidak tetap.flushCommands: Dipanggil pada saat "pelepasan tombol / pelepasan input", segera mengirim ulang perintah terakhir yang sudah menunggu dalam antrean; murni tidak melakukan apa pun ketika tidak ada perintah yang tertunda, tidak menghasilkan perintah baru.- Error (semua dimunculkan melalui
.errordarievents, bukanthrows): tidak ada pengalaman aktif103001; dipanggil dalam mode directing103003; saluran real-time belum siap / gagal mengirim105004.
pauseLocalAudioCapture() / resumeLocalAudioCapture()
- Tujuan: Melepaskan / memulihkan sementara pendudukan SDK pada penangkapan mikrofon lokal.
- Kapan digunakan: Ketika sesuatu seperti pengenalan suara memerlukan akses mikrofon eksklusif,
pauseterlebih dahulu danresumesetelahnya.
7. Event dan Status
Event dilampirkan pada OysterTravel.events dan merupakan saluran push aktif SDK kepada Anda, digunakan untuk memunculkan situasi yang tidak dipicu oleh panggilan Anda sendiri (misalnya masalah dengan koneksi real-time yang dikelola secara internal atau polling status).
var events: AsyncStream<OysterTravelEvent> { get }
@available(iOS 15.0, *)
public enum OysterTravelEvent {
case statusChanged(OysterTravelStatus)
// The SDK's internal flow errored, e.g. an internal API request error or a streaming error; note you must check for token expiration here and re-request the token
case error(OysterSDKError)
}
Ada dua gaya konsumsi, pilih salah satu:
- SwiftUI:
OysterTraveladalahObservableObject; gunakan@StateObject/@ObservedObjectsecara langsung untuk mengobservasistatusdan menggerakkan UI; error tetap berasal darievents. - Imperatif / UIKit: dalam
Task,for await event in travel.events { ... }, danswitchatas.statusChanged/.error; batalkanTaskyang ditahan setelah selesai.
let task = Task {
for await event in travel.events {
switch event {
case .statusChanged(let status): render(status) // see table below
case .error(let error): handle(error) // error.code / error.kind, see §9
}
}
}
// When done: task.cancel()
OysterTravelStatus (5 status proses + 2 status terminal):
Status | Deskripsi | Penanganan umum |
|---|---|---|
| setelah create, sebelum start | — |
| menghubungkan / menghubungkan ulang (menghubungkan / menghubungkan ulang internal) | tampilkan petunjuk koneksi / koneksi ulang |
| stream siap, interaktif (pemutaran internal) | tampilkan gambar dan kontrol |
| jeda diterima, menunggu konfirmasi server | tampilkan "pausing…" |
| dijeda (dikonfirmasi) | tampilkan status dijeda (hanya |
| berakhir (diakhiri aktif atau diakhiri sisi server). Terminal | akhiri dan tutup halaman |
| gagal. Terminal | tampilkan kesalahan dan akhiri |
CatatanSetelah memasuki ended / failed, sesi telah berakhir, dan semua operasi sesi (pause/resume/sendCommand…) tidak lagi berlaku. Callback dapat dipicu pada main thread, sehingga Anda dapat memperbarui UI secara langsung.
CatatanSelama polling status internal, SDK secara otomatis melaporkan heartbeat status pull/playback klien (connecting / playing / paused / reconnecting / disconnected), sehingga server dapat membedakan status sisi stream dari sisi klien — murni perilaku internal SDK, yang tidak perlu disadari atau diikutsertakan oleh host.
8. Model Data
CatatanSemua tipe publik ditandai dengan @available(iOS 15.0, *). Nilai/parameter pengembalian di bawah ini adalah output SDK, dibangun di tempat secara internal dengan tipe Swift native (Date / TimeInterval / OysterTravelStatus); semuanya bukan Codable dan tidak mengekspos detail wire (snake_case) — decoding wire terjadi di dalam SDK.
// Configuration
public struct OysterConfig: Sendable {
public let apiHost: String // Bailian gateway URL (not a business server), required, passed explicitly by the host
public let model: String // Versioned model name (e.g. "happyoyster-1.0-adventure"), required, no default
public let logLevel: OysterLogLevel? // .debug/.info/.warning/.error; defaults to .warning when nil
public let callbackTimeoutMs: Int // global callback timeout, default 30000; surfaced as 105005 on timeout
public init(apiHost: String, model: String, logLevel: OysterLogLevel? = nil, callbackTimeoutMs: Int = 30_000)
}
public enum OysterLogLevel: Int, Comparable, CaseIterable, Sendable {
case debug, info, warning, error
}
// External session status (§7)
public enum OysterTravelStatus: String, Equatable, Sendable, CustomStringConvertible {
case idle // after create, before start
case prepare // connecting / reconnecting
case running // stream ready, interactive
case pausing // pause accepted, awaiting server confirmation
case paused // paused (confirmed)
case ended // terminal: active end or server-side end
case failed // terminal: failed
}
// Open string value (preserves unknown values for server-side extension; encoded/decoded as a bare JSON string "running")
public struct OysterRawValue: RawRepresentable, Equatable, Hashable, Codable, Sendable {
public let rawValue: String
public init(rawValue: String) { self.rawValue = rawValue }
}
public typealias OysterModeValue = OysterRawValue
public extension OysterModeValue {
static let adventure = OysterModeValue(rawValue: "adventure")
static let directing = OysterModeValue(rawValue: "directing")
static let acting = OysterModeValue(rawValue: "acting")
}
// start() return (SDK output, not Codable)
public struct OysterStartTravelData: Equatable, Sendable {
public let encryptedTravelId: String // identifier for this experience
public let encryptedWorldId: String
public let mode: OysterModeValue // adventure / directing / acting
public let playUrl: String? // currently always returned as null by the server
public let firstFrame: String? // first-frame image URL, may be null (produced asynchronously)
public let version: String // world version identifier (diagnostics; drive interaction UI from `mode`)
public let aspectRatio: String? // "9:16" / "16:9"; only set for acting worlds, nil otherwise
}
// Control API returns (SDK output, not Codable; status is the external enum OysterTravelStatus)
public struct OysterTravelStateData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus }
public struct OysterRewindTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let resumedAtSec: TimeInterval }
public struct OysterEndTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let endedAt: Date; public let duration: TimeInterval }
public struct OysterSendInstructData: Equatable, Sendable { public let encryptedTravelId: String; public let content: String; public let accepted: Bool }
// Adventure-mode control command (strongly typed enums; values aligned with the internal WorldControlParams)
public struct OysterAdventureCommand: Equatable, Sendable {
public enum Translation: String { case none, front, back, left, right, frontLeft, frontRight, backLeft, backRight }
public enum Rotation: String { case none, mouseUp, mouseDown, mouseLeft, mouseRight, mouseUpLeft, mouseUpRight, mouseDownLeft, mouseDownRight }
public enum Interaction: String { case none, jump, attack, crouch, sprint }
public let translation: Translation // movement for this command; submit every frame while held, then stop
public let rotation: Rotation // view rotation for this command; submit every frame while held, then stop
public let interaction: Interaction // one-shot action for this command; submit once
public init(translation: Translation = .none, rotation: Rotation = .none, interaction: Interaction = .none)
public init(_ params: WorldControlParams) // convenient bridge from the default control WorldControlParams (consistent rawValue)
}
// Unified error (§9)
public struct OysterSDKError: Error {
public let code: Int
public let raw: Any? // the SDK's internal raw error info; structure is not guaranteed stable, for logging/diagnostics only
public var kind: OysterErrorKind // typed view of the numeric code, for exhaustive switch (computed property)
public init(code: Int, raw: Any? = nil)
// Error code constants are in the nested OysterSDKError.Code (e.g. .notInitialized = 100001)
}
// Typed semantic view of error codes: local codes are named cases; server-side 4xxxxx/5xxxxx converge to .server(code:)
public enum OysterErrorKind: Equatable, Sendable {
case notInitialized, tokenMissing, tokenInvalid, noActiveTravel, invalidState, sendCommandInDirecting
case concurrentTravel, realtimeConnectFailed, realtimeJoinTimeout, firstFrameTimeout
case channelNotReady, callbackTimeout, localNetwork, responseDecodeFailed
case streamAutoEnd, proxyOrUnrecognized, featureGateDisabled
case server(code: Int), unknown(code: Int)
}
Catatan
- Catatan: rawValues perintah menggunakan lower camelCase (misalnya
front/mouseLeft/jump); nilai enum di atas adalah yang otoritatif. - Catatan:
modesecara eksternal adalahadventure(wander) /directing(cerita) /acting(bermain peran); definisiOysterModeValueadalah yang otoritatif. - Catatan:
aspectRatioadalah string terbuka (saat ini9:16/16:9, lebih banyak mungkin ditambahkan). Uraikan sebagaiwidth:heightdan bandingkan rasionya alih-alih mencocokkan nilai yang diketahui.
9. Kode Error
SDK melaporkan error secara seragam sebagai OysterSDKError, dan tipenya selalu dibedakan oleh code — jangan menilai tipe dari "jalur mana error berasal": code yang sama dapat dilempar oleh metode bisnis (async throws) atau dimunculkan melalui .error dari events. Untuk pencocokan bertipe, gunakan error.kind (lihat §8 OysterErrorKind).
Kode error: server 4xxxxx/5xxxxx, client-local 1xxxxx.
Kode Error Server (umum)
code | Arti | Penanganan yang disarankan |
|---|---|---|
| Parameter tidak valid (nilai enum tidak valid, dll.) | Periksa parameter permintaan atau versi SDK |
| Kredensial pengalaman ( | Minta server menerbitkan ulang kredensial |
| Kredensial pengalaman ( | Kredensial sekali pakai; terbitkan ulang |
| World tidak ada, telah dihapus, atau bukan milik pengembang saat ini (termasuk world yang dihapus setelah kredensial diterbitkan) | Pilih World yang valid lagi |
| Status World belum siap | Tunggu hingga world siap sebelum memulai |
| API hanya mengizinkan API Key utama | Key sementara tidak dapat digunakan untuk API ini |
| Konten input ditolak oleh moderasi konten; berlaku untuk instruksi teks | Ubah input dan coba lagi |
| Spesifikasi layanan yang diminta tidak diaktifkan | Jangan mencoba lagi seolah-olah kapasitas penuh; beralih ke spesifikasi yang diaktifkan (biasanya: akun tidak memiliki spesifikasi acting) |
| Konfigurasi kapasitas sementara tidak tersedia | Coba lagi nanti |
| Sumber daya tidak ada (kepemilikan world/wander atau tidak ada artefak) | Verifikasi ID / status |
| Permintaan bertentangan dengan status sumber daya saat ini | Periksa status experience |
| Batas konkurensi tercapai untuk spesifikasi ini | Coba lagi setelah sesi yang ada berakhir (jangan bingung dengan |
| Kapasitas tersedia tidak mencukupi | Coba lagi nanti |
| Kesalahan sistem internal | Coba lagi nanti / laporkan |
| Alokasi sumber daya inferensi atau kegagalan layanan internal | Coba lagi nanti |
Kode Error Client-Local
code | Arti | SDK mengakhiri sesi secara otomatis | Penanganan yang disarankan |
|---|---|---|---|
| Dipanggil sebelum inisialisasi SDK; juga mencakup | Tidak (dilontarkan secara sinkron, menolak panggilan ini) |
|
| Token autentikasi HTTP tidak diinjeksikan | Tidak | coba lagi setelah |
| Token autentikasi HTTP tidak valid / ditolak | Tidak | tukar ulang token lalu coba lagi |
| Tidak ada experience aktif saat ini | Tidak (menolak panggilan ini) |
|
| Status/versi saat ini tidak mengizinkan operasi ini | Tidak (menolak panggilan ini) | periksa status experience / |
| Ketidakcocokan mode (misalnya | Tidak (menolak panggilan ini) | pilih API yang tepat berdasarkan |
| Create/start experience secara konkuren | Tidak (dilontarkan secara sinkron) | serialisasikan panggilan, |
| Koneksi real-time gagal | Ya | akhiri dan mulai ulang |
| Batas waktu bergabung real-time | Ya | akhiri dan mulai ulang |
| Batas waktu menunggu frame video pertama | Ya | akhiri dan mulai ulang |
| Channel real-time belum siap / pengiriman gagal | tergantung (kegagalan pengiriman aktif; heartbeat hanya untuk pelaporan) | kirim setelah |
| Batas waktu callback (default 30s) | Tidak | coba lagi, dan tingkatkan |
| Tidak ada stream setelah bergabung; SDK mengakhiri experience secara otomatis | Ya | akhiri dan mulai ulang |
| Kesalahan jaringan lokal | Tidak | dapat dicoba ulang |
| Gagal mengurai respons | tergantung | tingkatkan versi SDK / laporkan |
| Tidak ada kode error yang dapat dikenali / kode error string proxy | Tidak | dapat dicoba ulang |
| Dinonaktifkan dari jarak jauh oleh sakelar fitur server (penonaktifan penuh atau versi terlalu rendah; alasan di | Ya | Ikuti alasan di |
Cara menilai fatalitas: Fatalitas tidak lagi diekspos sebagai field boolean (OysterSDKError tidak memiliki isFatal). Secara semantik, "fatal" secara khusus berarti apakah SDK secara aktif mengakhiri sesi (memutuskan RTC, melepaskan seluruh sesi) —
- Error yang mengakhiri sesi secara otomatis (misalnya
105001/105002/105003/105006/108001): host menyadari hal ini dari status terminal state-machine (status → failed, dimunculkan melalui.statusChangeddarievents), dan kembali ke layar sebelum "mulai pengalaman" sesuai dengan itu, tanpa perlu menilai fatalitasnya sendiri. - Error penolakan panggilan (
100001/103001/103002/103003/103004): dilempar secara sinkron / panggilan ditolak saat Anda secara aktif memanggil; error ini tidak mengakhiri sesi. - Error non-fatal lainnya (misalnya
101001/101002/105005/106001/106003): error ini tidak mengakhiri sesi; coba lagi seperti yang disarankan atau lanjutkan setelah menyuntikkan ulang token.
Catatan106001 mungkin memiliki dua penyebab: error jaringan lokal, atau apiHost yang salah. Jika mencoba lagi tidak memulihkan permintaan, periksa apakah apiHost dikonfigurasi dengan benar.
Catatan106003 yang muncul setelah Anda mengatur model biasanya berarti nama/versi model salah atau tidak diaktifkan untuk akun Anda: periksa model bersama dengan apiHost dan token, yang semuanya harus berasal dari akun dan wilayah yang sama — gateway menolak ketidakcocokan dengan AccessDenied, dinormalisasi ke kode ini.