Skip to main content
SDK Android

Panduan Integrasi Android SDK HappyOyster

Dengan mengintegrasikan Android SDK HappyOyster, Aplikasi Anda dapat memasuki dunia yang dihasilkan oleh AI secara real-time, menghadirkan pengalaman video interaktif real-time dalam tiga mode: Adventure (eksplorasi dunia), Directing (pengarahan real-time), dan Acting (bermain peran).

Happy Oyster adalah produk penjelajahan dunia AI. Dengan mengintegrasikan Happy Oyster Android SDK, aplikasi Anda dapat memasuki "dunia" yang dihasilkan oleh AI secara real-time, menghadirkan pengalaman video interaktif real-time dalam adventure, directing, atau acting. Dokumen ini ditujukan untuk pengembang Android di sisi integrasi. Dokumen ini mencakup instalasi, autentikasi, alur integrasi lengkap, penanganan event, dan praktik terbaik, yang menjelaskan cara berintegrasi secara andal. Untuk API spesifik (tanda tangan, parameter, nilai kembalian, kode error, model data), lihat Referensi API Happy Oyster Android SDK.

1. What You Can Do

  • Mulai pengalaman (Travel): Masuk ke dunia yang siap dengan kredensial sekali pakai; SDK secara otomatis membangun koneksi video real-time.
  • Pemutaran real-time: SDK mengembalikan View video yang Anda pasang ke dalam tata letak untuk memutar visual yang dihasilkan AI secara real-time.
  • Real-time interaction:
    • mode directing (directing): Kirim instruksi teks untuk menggerakkan alur cerita.
    • acting: Kirim juga instruksi teks; jeda/lanjutkan tersedia; jangan putar ulang dan jangan gunakan sendCommand. Gunakan aspectRatio dari respons gabung untuk mengatur orientasi pemain (lihat §13 Adaptasi Mode).
    • mode adventure (adventure): Kirim perintah kontrol arah/sudut pandang/tindakan untuk berinteraksi dengan dunia.
  • Kontrol proses: Jeda / lanjutkan (directing dan acting), mundur (hanya directing), akhiri (ketiga mode).
  • Callback status dan error: Pantau status dan pengecualian pengalaman secara real-time melalui event listener.
SDK tidak bertanggung jawab atas pembuatan dan pengelolaan dunia, maupun secara langsung mengekspos detail komunikasi real-time tingkat rendah—ini ditangani oleh server Anda atau secara internal oleh SDK. Anda hanya perlu fokus pada "mulai pengalaman → putar → interaksi → akhiri".

2. Installation

Happy Oyster SDK (cn.happyoyster:opensdk) dipublikasikan di Maven Central; mesin komunikasi real-time yang mendasarinya (Alibaba Cloud ARTC) dipublikasikan di Alibaba Cloud Maven. Kedua repositori harus dideklarasikan.

Persyaratan Lingkungan

Item

Requirement

minSdk

24 (Android 7.0) dan lebih tinggi

compileSdk

36

JDK

Target bytecode JDK 11 (toolchain host: JDK 11 atau lebih tinggi direkomendasikan)

Language

Kotlin (API coroutine suspend)

ABI

arm64-v8a / armeabi-v7a (mesin komunikasi real-time berisi native library)

Network

Akses internet publik diperlukan

Tambahkan repositori di settings.gradle.kts root proyek:
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()                                       // Happy Oyster SDK (cn.happyoyster:opensdk)
        maven("https://maven.aliyun.com/repository/public")  // real-time communication engine (Alibaba Cloud ARTC)
    }
}
Temukan rilis terbaru yang dipublikasikan di Maven Central, ganti <version> di bawah ini dengan nomor versi yang tepat, dan tambahkan dependensi di modul build.gradle.kts:
dependencies {
    implementation("cn.happyoyster:opensdk:<version>")
}
Sematkan dependensi ke versi yang tepat. Tinjau catatan rilis sebelum meningkatkan, dan kompilasi ulang kode host setelah meningkatkan.
SDK dipublikasikan sebagai AAR tipis dan tidak menyematkan dependensi pihak ketiga; dependensi transitif seperti mesin komunikasi real-time ditarik secara otomatis dari repositori di atas selama resolusi, sehingga repositori Alibaba Cloud Maven sangat penting—ketiadaannya akan menyebabkan com.aliyun.aio:AliVCSDK_ARTC gagal diresolusi.

Permissions

Library SDK itu sendiri hanya mendeklarasikan INTERNET. Mesin komunikasi real-time secara otomatis menggabungkan sejumlah kecil izin tipe jaringan/Bluetooth/pengaturan audio; manifes library tidak menyertakan izin mikrofon atau kamera. Stream video adalah pemutaran khusus langganan, dan SDK tidak mengirim audio nyata ke sisi remote. Host harus mendeklarasikan sendiri: Ketika targetSdk Anda adalah 33 atau lebih tinggi, Anda harus mendeklarasikan izin notifikasi di AndroidManifest.xml host (mesin komunikasi real-time menyertakan layanan latar depan):
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
mode petualangan (sendCommand) — mendeklarasikan izin mikrofon disarankan: Perintah kontrol real-time mode petualangan menggunakan uplink yang dibawa oleh stream audio senyap yang menetapkan "identitas penerbit" lokal; SDK tidak merekam atau mengunggah audio asli Anda. RECORD_AUDIO bukanlah prasyarat mutlak untuk DataChannel—bahkan tanpanya, stream senyap tetap berdiri sebagai penerbit dan uplink biasanya tersedia. Namun, untuk stabilitas di berbagai perangkat, kami menyarankan bahwa, jika aplikasi Anda menggunakan sendCommand, Anda mendeklarasikan izin di host AndroidManifest.xml dan memintanya saat runtime sebelum memanggil.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Tentang kode error 105004: 105004 berarti saluran real-time belum siap / pengiriman gagal (misalnya interupsi DataChannel atau anomali saluran real-time). Ini bukanlah konsekuensi yang pasti dari izin yang hilang—ini tidak dipicu secara langsung oleh RECORD_AUDIO yang tidak diberikan. Mode pengarahan (sendInstruct) dan pemutaran video tidak terpengaruh. Jika aplikasi Anda tidak menggunakan mode petualangan, izin ini tidak diperlukan. Untuk memangkas izin yang digabungkan, gunakan tools:node="remove".

3. Model Autentikasi

SDK tidak memperoleh atau menyegarkan token, sehingga tetap ringan. Autentikasi memiliki dua lapisan:
  1. API Key gateway Bailian (百炼): Disisipkan sebagai token Bearer oleh aplikasi Anda melalui updateToken(token). SDK hanya menyimpan yang terbaru; SDK tidak menyimpannya secara permanen atau menyegarkannya, dan setelah Key berubah, Anda harus menyisipkannya kembali. Gateway mensyaratkan bahwa semua permintaan SDK, termasuk startTravel, menggunakan Bearer ini. Ketika Bearer Key kedaluwarsa atau tidak valid, SDK memunculkan SDKError(101002); dalam kasus ini, Anda harus mendapatkan kembali dan menyisipkan kembali Bearer Key (updateToken) alih-alih menukarkan tiket baru. Selama pengembangan / Demo, Anda dapat menggunakan API Key utama Bailian (百炼) Anda secara langsung sebagai Bearer (updateToken) agar alur dapat berjalan. Di lingkungan produksi, selalu beralih ke token berumur pendek yang diterbitkan oleh server Anda, dan jangan pernah menyertakan API Key berumur panjang ke dalam aplikasi yang didistribusikan.
  2. Kredensial pengalaman satu kali ticket: Dipertukarkan oleh server Anda melalui platform terbuka dan dikirimkan ke klien; hanya digunakan untuk satu startTravel saja. Kredensial ini menjadi tidak valid setelah pengalaman berakhir (secara normal atau tidak normal) dan tidak dapat digunakan kembali. Error kredensial tingkat tiket diidentifikasi oleh kode server enam digit (misalnya, 401010 = tiket tidak valid / kedaluwarsa, 401011 = tiket sudah digunakan), yang diteruskan oleh SDK apa adanya.
Server Anda bertanggung jawab untuk membuat / mengelola World, menukarkan Travel ticket, dan mengirimkannya ke klien; Android SDK hanya menggunakan token dan tiket serta tidak menyediakan antarmuka pengelolaan World. Selama inisialisasi, Anda harus meneruskan API Host akun Bailian (百炼) Anda melalui SDKConfig.apiHost (berbentuk llm-xxxx.ap-southeast-1.maas.aliyuncs.com, disalin dari kolom "API Host" pada halaman API Key di konsol Bailian (百炼)) dan meneruskan SDKConfig.model yang wajib diisi dan tanpa nilai default (happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure, sesuai dengan entri Open API). SDK melengkapi URL permintaan sebagai https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}, sehingga Anda tidak perlu menyusunnya sendiri. Menghilangkan model saat membuat SDKConfig adalah kesalahan waktu kompilasi. Jika model kosong, initialize secara sinkron memunculkan SDKError(100002) (raw = "SDKConfig.model must not be blank"), tidak membuat atau mengganti runtime, dan tidak membuat permintaan jaringan. API Host, model, dan API Key yang disisipkan harus sesuai dengan akun, wilayah, dan otorisasi model yang dipersyaratkan, jika tidak, gateway biasanya mengembalikan AccessDenied (pada runtime ini dimunculkan sebagai SDKError(106003), dengan body AccessDenied asli tersedia di SDKError.raw; lihat baris 106003 pada tabel kode kesalahan Referensi API untuk daftar periksa pemecahan masalah). Untuk keamanan, kami sangat menyarankan klien untuk menyisipkan token berumur pendek yang diterbitkan oleh server Anda sebagai Bearer, alih-alih menyertakan API Key berumur panjang ke dalam aplikasi atau commit ke repositori kode.
Pemberitahuan kepatuhan regional: Untuk mendukung kepatuhan terhadap persyaratan hukum dan data yang berlaku, jika pengguna target Anda mencakup pengguna AS, Anda harus mengonfigurasi API Host wilayah AS selama inisialisasi SDK untuk layanan yang disediakan kepada pengguna tersebut. Pengembang bertanggung jawab atas konfigurasi yang benar dan menanggung tanggung jawab yang sesuai berdasarkan hukum yang berlaku jika gagal mengikuti persyaratan ini.

4. Quick Start

import cn.happyoyster.opensdk.*   // All entry-point classes live in this package: HappyOyster, SDKConfig, TravelStatusValue, ModeValue, SDKError, etc.

// Track the current experience status and this Travel's metadata; use them to decide whether interaction can be sent.
@Volatile private var currentStatus: TravelStatusValue? = null
@Volatile private var currentTravel: StartTravelData? = null

// 1) Initialize (recommended in Application.onCreate)
//    apiHost is required: your account's Bailian API Host (copied from the API Key page in the Bailian console)
//    model is required with no default: the complete model name and version; refer to the official Bailian HappyOyster model documentation for available values
HappyOyster.initialize(
    applicationContext,
    SDKConfig(
        apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com",
        model = "happyoyster-1.0",
    ),
)

// 2) Inject the Bailian gateway API Key (as the Bearer token)
HappyOyster.updateToken(bailianApiKey)

// 3) Listen to SDK events: use onStatusChanged to drive the host state machine and gate interaction capabilities
HappyOyster.addListener(object : HappyOysterListener {
    override fun onStatusChanged(status: TravelStatusValue) {
        currentStatus = status
        when (status) {
            // Interaction is allowed only in running; adventure-mode sendCommand is valid only in running.
            TravelStatusValue.Running -> markInteractionAllowed()
            // Paused is the signal that pauseTravel has actually taken effect (asynchronous, see below); sendInstruct is still allowed here.
            TravelStatusValue.Paused -> markTravelPaused()
            // Terminal states: the SDK has already ended and released resources; clean up the host-side experience state.
            TravelStatusValue.Completed, TravelStatusValue.Failed -> clearActiveTravel()
            // init / pending: not yet ready, interaction unavailable.
            else -> markInteractionBlocked()
        }
    }
    override fun onError(error: SDKError) {
        // Unified error callback; see the error-codes section of the API Reference
    }
})

// 4) Start the experience (the ticket is delivered by your server)
lifecycleScope.launch {
    try {
        // Only one concurrent Travel is allowed per App at a time (alirtc limitation: even with a different ticket
        // you cannot start a second Travel concurrently within the same App). Calling again while an experience is in progress throws
        // SDKError(103004), whose raw carries only a redacted summary of the ticket currently playing, never the full ticket.
        val travel: StartTravelData = HappyOyster.startTravel(ticket)
        currentTravel = travel

        // Inspect the returned Travel metadata first, then decide how to interact:
        //   travel.mode          —— directing / adventure / acting
        //   travel.creationModel —— Simple / ScriptList
        //   travel.aspectRatio   —— Acting only; use to set player orientation
        val canSendInstruct = (travel.mode == ModeValue.Directing || travel.mode == ModeValue.Acting) &&
            travel.creationModel != CreationModelValue.ScriptList
        // Note: calling sendInstruct in ScriptList mode throws SDKError(103002); script content is not managed through this SDK.

        // 5) Mount the video View (the SDK returns the view; you add it to the layout)
        //    Acting: size the container from travel.aspectRatio first, then attachVideo() (see §13 Mode Adaptation) —
        //    the remote view is bound clip-to-fill, so a mismatched container orientation crops the picture.
        val videoView = HappyOyster.attachVideo()
        binding.videoContainer.addView(videoView)

        // 6) In-run interaction — you must wait for running before sending:
        //    - sendCommand (adventure): valid only in running; calling in init/pending/paused returns 103002.
        //    - sendInstruct (directing / Acting): valid in running or paused; calling in init/pending returns 103002.
        if (canSendInstruct && currentStatus == TravelStatusValue.Running) {
            HappyOyster.sendInstruct("突然下起了大雨")
        }
    } catch (e: SDKError) {
        // Handle start failure (e.g., 103004: an experience is already playing)
    }
}

// 7) Pause / Resume (directing and Acting; rewind is directing only)
//    pauseTravel is asynchronous: the method's return only means "accepted"; the actual pause is determined by onStatusChanged(paused).
//    Be sure to wait for the paused callback before allowing resumeTravel.
lifecycleScope.launch {
    if ((currentTravel?.mode == ModeValue.Directing || currentTravel?.mode == ModeValue.Acting) &&
        currentStatus == TravelStatusValue.Running
    ) {
        HappyOyster.pauseTravel()        // accepted; the host marks pausing and waits for the status callback
        // …after receiving onStatusChanged(Paused)…
    }
}
lifecycleScope.launch {
    if (currentStatus == TravelStatusValue.Paused) {
        HappyOyster.resumeTravel()       // call only after paused
    }
}

// 8) End the experience (the SDK automatically disconnects the real-time connection and releases resources)
lifecycleScope.launch { HappyOyster.endTravel() }

5. Langganan Event & Penanganan Error

Gunakan onStatusChanged untuk menggerakkan state machine host dan membatasi kemampuan interaksi; gunakan onError untuk menerima error runtime secara seragam. Untuk antarmuka event dan kode error lengkap, lihat Referensi API Happy Oyster Android SDK. Must
  • Daftarkan listener segera setelah HappyOyster.initialize(...) berhasil dikembalikan; addListener / removeListener harus dipanggil setelah initialize (memanggilnya sebelum inisialisasi akan melempar SDKError(100001)).
  • Gerbangkan pada onStatusChanged(Running)—pemanggilan interaksi hanya diizinkan setelah running; sendCommand mode adventure hanya valid dalam running.
  • Tangani juga onError; jangan hanya menangkap startTravel. Error fatal juga mengakhiri pengalaman (lihat bagian kode error pada Referensi API).
Recommended
  • Panggil removeListener pada titik siklus hidup yang sesuai (seperti onDestroy) untuk menghindari kebocoran memori.
Avoid
  • Mendaftarkan listener secara berulang tanpa memanggil removeListener.

6. Mengirim Instruksi: sendInstruct dan sendCommand

sendInstruct (directing / acting)

sendInstruct digunakan dalam pengarahan dan akting untuk mengirimkan instruksi teks yang menggerakkan gambar; ini dapat dipanggil dalam status running atau paused. Dalam status jeda, SDK tidak melanjutkan secara otomatis—host memutuskan apakah akan memanggil resumeTravel terlebih dahulu dan kemudian mengirimkan instruksi (untuk detail kontrak seperti pembatasan dan validasi status, lihat Referensi API). creationModel dunia akting selalu Simple, sehingga batasan ScriptList hanya berlaku untuk pengarahan.

sendCommand (mode adventure)

sendCommand digunakan dalam mode petualangan (adventure) untuk mengirimkan perintah kontrol arah/sudut pandang/tindakan; ini hanya valid dalam running. SDK memiliki throttle latest-wins bawaan 42 ms (24 fps), sehingga host dapat memanggilnya pada frame rate game dan SDK akan menggabungkannya secara otomatis; tidak diperlukan pembatasan rate secara manual. Panggil sekali untuk tindakan satu kali. Untuk tindakan yang ditahan, terus panggil setiap frame saat ditahan dan kirimkan secara eksplisit satu reset None saat dilepaskan (untuk kontrak pembatasan dan penampilan kegagalan yang terperinci, lihat Referensi API). Praktik yang disarankan: Dalam UI interaksi mode petualangan host, sediakan tiga titik masuk perintah independen (arah pergerakan + arah sudut pandang + interaksi tindakan), pertahankan nilai yang saat ini dipegang untuk setiap grup, dan kirimkan snapshot tiga bidang yang lengkap pada setiap panggilan. Gunakan "None" hanya untuk dimensi yang tidak aktif. Ketika input dari dimensi yang berbeda ditahan secara bersamaan, pertahankan dan kirimkan semua nilai aktif alih-alih mengatur ulang dimensi lain ke "None".

7. Jeda / Lanjutkan / Putar Ulang

Mode yang berlaku: jeda / lanjut berlaku untuk pengarahan dan akting, dan berperilaku identik pada keduanya (termasuk penghalang 3 detik di bawah ini); mundur hanya untuk pengarahan. Memanggil pauseTravel / resumeTravel / rewindTravel dalam mode petualangan ditolak oleh SDK dengan 103003. Selain itu, version yang dilaporkan oleh server harus berupa token v2 yang diperlukan oleh modenya (storyV2 untuk pengarahan, actingV2 untuk akting); jika tidak, jeda / lanjut akan mengembalikan 103002. Jeda bersifat asinkron: Pengembalian dari metode pauseTravel hanya berarti telah diterima; jeda yang sebenarnya ditentukan oleh onStatusChanged(Paused). Antara "memanggil pauseTravel" dan "menerima panggilan balik Paused", host dapat menandai status pengalaman lokalnya sebagai pausing; hanya setelah menerima Paused Anda seharusnya mengizinkan pemanggilan resumeTravel atau rewindTravel yang bergantung pada status jeda. Penghalang jeda→buka kembali internal SDK: setelah Paused diterima, host dapat memanggil resumeTravel / rewindTravel sesuai dengan kontrak. Jika panggilan jatuh di dalam jendela penyelesaian 3 detik setelah konfirmasi jeda, metode penangguhan SDK menunggu sisa waktu sebelum mengirimkan permintaan yang membuka kembali ruang real-time. Host tidak memerlukan penundaan jeda→lanjut ekstra, dan tidak ada penundaan yang ditambahkan setelah 3 detik berlalu secara alami. Cooldown pemanggilan sisi host (direkomendasikan): Disarankan agar host menunda penerbitan pauseTravel lain dalam 3 s setelah resumeTravel berhasil dikembalikan, untuk menghindari peralihan yang terlalu sering. SDK itu sendiri tidak memberlakukan cooldown ini. Mundur: rewindTravel hanya tersedia dalam status paused; setelah mundur, server melanjutkan secara otomatis dan SDK menyambungkan ulang RTC secara otomatis, tanpa perlu intervensi host. Urutan umum: pause → wait:paused → rewindTravel(sec). Detik mundur rewindToSec harus merupakan kelipatan dari 4 (misalnya, 4, 8, 12); nilai yang bukan kelipatan dibulatkan ke bawah oleh server (misalnya, 7→4), dan detik efektif yang sebenarnya ditentukan oleh resumedAtSec yang dikembalikan. Mundur hanya untuk pengarahan: memanggil rewindTravel dalam mode akting atau petualangan ditolak oleh SDK secara lokal dengan 103003, tanpa mengeluarkan permintaan HTTP apa pun; host sebaiknya menyembunyikan entri mundur alih-alih hanya mengaburkan tombol. Untuk detail kontrak seperti semantik asinkron dan backoff percobaan ulang 3× (backoff resumeTravel sebesar 1 s / 2 s / 3 s), lihat Referensi API Happy Oyster Android SDK.

8. Logging

Tag Logcat SDK: HappyOysterSDK. SDK menyediakan dua jalur output log yang independen:
  • Logcat bawaan (nonaktif secara default): SDK menulis ke Logcat hanya ketika SDKConfig.logcatEnabled = true (default false, sepenuhnya senyap). SDKConfig.logLevel memfilter level minimumnya; INFO default sudah membawa jangkar siklus hidup yang diperlukan untuk merekonstruksi sesi (inisialisasi, awal Travel / transisi status / akhir, gabung RTC / frame pertama). Turunkan ke WARN untuk error saja, atau tingkatkan ke DEBUG / VERBOSE untuk diagnosis yang lebih mendalam. logLevel tidak memengaruhi logHandler.
  • Panggilan balik host logHandler (disarankan): menerima setiap LogRecord dari SDK (semua aliran data), sepenuhnya independen dari logLevel / logcatEnabled, dan dapat diteruskan ke sistem logging host sendiri (Logcat, file, platform crash, dll.). Panggilan balik harus cepat dan tidak memblokir serta tidak boleh memanggil kembali ke SDK; pengecualian yang dilemparkannya ditangkap secara senyap; LogRecord.message disamarkan dan tidak berisi token Bearer, ticket, atau token RTC dalam teks biasa.
HappyOyster.initialize(
    context,
    SDKConfig(
        apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com",  // required: your account's Bailian API Host
        model = "happyoyster-1.0",                          // required with no default: complete model name and version
        logcatEnabled = true,        // enable built-in Logcat (default false)
        logLevel = LogLevel.DEBUG,   // affects only the built-in Logcat
        logHandler = { record ->     // optional: forward to the host's own Logcat tag
            android.util.Log.d("MyApp/SDK", record.message, record.throwable)
        },
    ),
)
adb logcat -s HappyOysterSDK          # output only when built-in Logcat is enabled
adb logcat -s HappyOysterSDK MyApp    # also view the host App logs (replace MyApp with your tag)
Korelasi sesi dan permintaan: travelId (yaitu encryptedTravelId) dalam log SDK dipancarkan secara utuh dan merupakan kunci sesi yang dikirim ke server — sertakan saat menyelidiki sesi atau mengajukan tiket untuk menyelaraskan log klien dan server. Setiap baris respons HTTP juga membawa requestId server (bidang reqId=, ditampilkan sebagai - jika tidak ada) untuk menentukan satu permintaan secara tepat; ID ini hanya muncul di log dan tidak pernah ditampilkan pada nilai pengembalian publik apa pun.

9. Siklus Hidup & Memori

  • Panggil initialize di Application.onCreate, satu kali secara global.
  • Inisialisasi ulang saat idle (misalnya untuk beralih wilayah API atau model) menutup runtime idle sebelumnya dan mengatur ulang listener yang terdaftar serta status feature-gate; daftarkan ulang listener setelah metode ini kembali. Inisialisasi ulang ditolak dengan 103004 saat Travel sedang dimulai, aktif, atau berakhir: selalu tunggu endTravel() terlebih dahulu. SDK tidak pernah membuang Travel yang aktif sebagai efek samping dari initialize.
  • Pengalaman terikat pada siklus hidup host: panggil endTravel di onDestroy dari Activity/Fragment (atau onCleared ViewModel) untuk memastikan koneksi dan sumber daya real-time dilepaskan.
  • Hapus View yang dikembalikan oleh attachVideo() dari tata letak saat berakhir (container.removeAllViews()), dan panggil removeListener.
  • SDK hanya menyimpan application context; demikian juga, jangan meneruskan Activity ke SDK.

10. Coroutines & Threading

  • Method bisnis adalah suspend; panggil di lifecycleScope / viewModelScope dari konteks coroutine mana pun. SDK mengoordinasikan status dan pekerjaan RTC pada dispatcher utamanya sementara HTTP tetap berada di luar main thread.
  • Jika coroutine pemanggil dibatalkan, SDK membatalkan permintaan HTTP yang sedang berjalan dari pemanggilan tersebut, jika ada, dan meneruskan CancellationException tanpa perubahan alih-alih mengonversinya ke SDKError; jangan menangkap atau menelannya sebagai kegagalan operasional. Pembatalan tidak membatalkan permintaan yang telah diterima oleh server.
  • Panggil attachVideo() dan sendCommand() pada main thread; pemanggilan di luar main thread akan melempar IllegalStateException secara sinkron.

11. Manajemen Token

  • Bearer API Key memiliki masa berlaku yang terbatas; disarankan untuk memastikan token masih baru sebelum memasuki pengalaman. Saat menerima onError(101002) (token kedaluwarsa), dapatkan kembali Bearer Key dan panggil updateToken—tidak perlu menukar dengan tiket baru.

12. Pemulihan Error

  • Untuk error fatal: bersihkan status pengalaman saat ini (termasuk menghapus View video), beri tahu pengguna, dan izinkan memulai ulang.
  • Untuk jitter jaringan (106001) dan anomali layanan hulu sementara (106003): sejumlah terbatas percobaan ulang dapat dilakukan.
  • 100002 sinkron dari inisialisasi berarti model kosong; berikan nama dan versi model yang lengkap, lalu inisialisasi ulang. Jika model yang tidak kosong memiliki nama atau versi yang salah, telah dihentikan, belum dipublikasikan, atau tidak diotorisasi, gateway biasanya mengembalikan AccessDenied, yang dipetakan ke 106003; gunakan SDKError.raw dan verifikasi bahwa model, API Host, API Key, akun, dan wilayah cocok.
(Untuk klasifikasi fatal / non-fatal, lihat bagian kode error dari Referensi API Happy Oyster Android SDK.)

13. Adaptasi Mode

  • Gunakan mode yang dikembalikan oleh startTravel untuk memutuskan kemampuan interaksi mana yang akan diekspos: directing dan acting menggunakan instruksi teks sendInstruct (acting juga menggunakan aspectRatio untuk orientasi pemain dan menyembunyikan putar ulang); mode adventure menggunakan perintah kontrol sendCommand.
  • Dalam mode petualangan, Anda dapat menggunakan overload startTravel(ticket, maxExperienceTimeSec) untuk membatasi durasi maksimum pengalaman ini (detik; sesi berakhir secara otomatis saat tercapai). Nilai yang diizinkan dikonfigurasi oleh server (saat ini 60 / 90 / 120, default 60); pengarahan dan akting mengabaikan nilai tersebut (server mengabaikannya dan menggemakan null). Meneruskan nilai yang tidak didukung membuat server mengembalikan 400000 dan awal ini gagal. Lihat Referensi API untuk detail parameter.

acting: Mengatur Orientasi Pemain dari aspectRatio

StartTravelData.aspectRatio bukan null hanya untuk akting: "9:16" (potret, default sisi server saat dunia dibuat) atau "16:9" (lanskap); nilainya null untuk petualangan dan pengarahan. Kanvas ditetapkan saat dunia dibuat (ditentukan oleh server Anda melalui Open API), sehingga bagi klien ini adalah hasil baca-saja. SDK mempertahankan nilai yang tidak dikenali apa adanya, sehingga host harus memperlakukan nilai apa pun yang tidak dikenalnya seperti null dan kembali ke orientasi defaultnya sendiri. Waktu: tentukan orientasi kontainer pemutaran setelah startTravel() kembali dan sebelum memanggil attachVideo() serta memasang SurfaceView yang dikembalikan ke dalam tata letak Anda. SDK hanya mulai mengikat dan merender stream jarak jauh setelah tampilan host dilampirkan, sehingga menentukan orientasi pada titik tersebut masih mendahului frame yang dirender pertama kali. Nilai ini dikirimkan bersama nilai pengembalian startTravel() dan tidak dijamin tiba sebelum SDK bergabung dengan saluran komunikasi real-time — nilai ini hanya harus diterapkan sebelum attachVideo(). Konsekuensi: remote view terikat dengan mode render clip-to-fill — container yang orientasinya tidak sesuai dengan aspectRatio akan memotong gambar (misalnya, stream potret 9:16 yang ditempatkan dalam container 16:9 akan kehilangan sebagian besar bagian atas dan bawahnya) alih-alih memberinya letterbox.
// After startTravel() returns and before attachVideo(): size the container from aspectRatio first
val ratio: Float = when (travel.aspectRatio) {   // width / height
    "9:16" -> 9f / 16f                           // portrait (the Acting server-side default)
    "16:9" -> 16f / 9f                           // landscape
    else -> HOST_DEFAULT_RATIO                   // null or unrecognized: fall back to your own default orientation
}
// Apply `ratio` to the container, for example:
//   - Compose: Modifier.fillMaxWidth().aspectRatio(ratio)
//   - Views: put the container in a ConstraintLayout, full width, height driven by the ratio (height=0dp in XML)
binding.videoContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
    dimensionRatio = ratio.toString()
}

// Only once the container orientation is settled, mount the video View returned by the SDK
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
Ketika acting tidak diaktifkan untuk akun (atau spesifikasi dimatikan), pembuatan dan penggabungan dunia akan ditolak oleh server: startTravel gagal dengan 403007 (ditolak sebelum Travel dibuat), yang diteruskan oleh SDK apa adanya. Jangan mencoba ulang kode ini sebagai kapasitas penuh; minta pengguna untuk meminta pengaktifan sebagai gantinya.

14. Contoh Lengkap (Snippet ViewModel + Activity)

class TravelViewModel : ViewModel() {

    private val listener = object : HappyOysterListener {
        override fun onStatusChanged(status: TravelStatusValue) {
            _status.value = status
        }
        override fun onError(error: SDKError) {
            _error.value = error
        }
    }

    init { HappyOyster.addListener(listener) }

    fun start(ticket: String) = viewModelScope.launch {
        try {
            val travel = HappyOyster.startTravel(ticket)
            _travel.value = travel
        } catch (e: SDKError) {
            _error.value = e
        }
    }

    fun send(text: String) = viewModelScope.launch {
        runCatching { HappyOyster.sendInstruct(text) }
    }

    fun stop() = viewModelScope.launch { runCatching { HappyOyster.endTravel() } }

    override fun onCleared() {
        HappyOyster.removeListener(listener)
        viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
    }
}

// Mount the video in the Activity
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// On end
binding.videoContainer.removeAllViews()

15. Panduan Pemecahan Masalah DevOps

Ketika terjadi kesalahan selama integrasi (tidak dapat memasuki Travel, layar hitam tanpa video, stream terputus di tengah sesi, anomali jeda/lanjutkan, ...), membuka log SDK adalah cara tercepat untuk melokalisasi masalah. Bab ini memberikan alur pemecahan masalah standar — berguna baik untuk pemeriksaan mandiri Anda maupun untuk memberikan kami informasi yang cukup dalam satu putaran.

15.1 Mengaktifkan log

Pilih output log sesuai kebutuhan saat memecahkan masalah (lihat §8 Logging): untuk pemeriksaan mandiri cepat, atur logcatEnabled = true dan baca dengan adb logcat, tingkatkan logLevel ke DEBUG atau VERBOSE saat mendiagnosis (INFO default sudah membawa linimasa siklus hidup penuh; turunkan ke WARN untuk error saja); untuk memasukkan ke sistem Anda sendiri, gunakan logHandler untuk menerima setiap LogRecord (tidak terpengaruh oleh logLevel) dan tulis ke file atau platform crash Anda.

15.2 ID korelasi sesi: travelId

travelId (yaitu StartTravelData.encryptedTravelId) dalam log SDK diemisikan secara lengkap. Ini adalah satu-satunya identifier yang berjalan melalui satu sesi dan kunci sesi yang dikirim ke server. Ini adalah kunci untuk menyelaraskan log klien dengan catatan sesi sisi server — selalu sertakan travelId dari sesi yang gagal saat melaporkan masalah.

15.3 Mengumpulkan log

Saat mereproduksi masalah, simpan log SDK ke dalam file:
# Clear history, reproduce the issue, then capture SDK logs to a file
adb logcat -c
adb logcat -s HappyOysterSDK > happyoyster-sdk.log
# To also see your own app logs (replace MyApp with your tag):
adb logcat -s HappyOysterSDK MyApp > happyoyster-sdk.log

15.4 Apa yang perlu disertakan saat melaporkan masalah

Untuk menghindari beberapa kali bolak-balik komunikasi, harap lampirkan hal berikut:

Item

Notes

SDK version

HappyOyster.VERSION

Session travelId

encryptedTravelId dari sesi yang gagal (lihat 15.2)

Time of occurrence

perkiraan waktu (tingkat menit sudah cukup)

Kode error

SDKError.code yang ditangkap (arti dalam tabel kode error Referensi API)

Reproduction steps

jalur tindakan + hasil yang diharapkan + hasil aktual

Environment

model perangkat, versi Android, jaringan (WiFi/seluler)

Log file

log HappyOysterSDK dari 15.3 (DEBUG/VERBOSE direkomendasikan)

15.5 Tentang penyuntingan dan kemampuan berbagi log

Log SDK dirancang agar aman untuk dibagikan: kredensial (token Bearer, tiket Travel, token RTC), pengenal internal RTC, dan URL media disamarkan sebelum ditulis, sehingga tidak pernah muncul dalam teks biasa; travelId adalah pengenal sesi (bukan kredensial) dan disimpan secara utuh hanya untuk korelasi. Meskipun demikian, kami menyarankan untuk mentransfer file log melalui saluran tepercaya daripada menempelkannya secara publik di platform yang tidak terkendali.
Pembuatan Gambar
  • FAQ
Video Generation
Model Dunia
Audio
  • Pembuatan audio
Realtime API
Penyematan Teks
TokenPlan
Model production