全部产品
Search
文档中心

Cloud Phone:Putar layar cloud phone secara lokal menggunakan SDK

更新时间:Dec 17, 2025

Dalam skenario cloud phone, pengguna mengoperasikan perangkat virtual yang berjalan di cloud, sedangkan klien lokal hanya berfungsi sebagai medium untuk tampilan dan interaksi. Ketika orientasi layar cloud phone berubah—misalnya dari portrait ke landscape—pengalaman pengguna akan sangat terganggu jika klien lokal tidak merespons secara sinkron. Hal ini dapat menyebabkan masalah tampilan seperti citra terbalik, peregangan, atau bilah hitam, serta ketidaksesuaian dalam interaksi. Topik ini menjelaskan cara mengimplementasikan mekanisme sinkronisasi yang presisi pada SDK klien Android dan iOS, yang menyelaraskan orientasi layar lokal dengan instruksi rotasi dari cloud sehingga antarmuka lokal dan aliran video selalu sesuai dengan orientasi perangkat cloud, menciptakan pengalaman operasi remote yang imersif dan mulus.

Latar Belakang

Aplikasi mobile tradisional biasanya mengandalkan sensor perangkat atau konfigurasi sistem untuk merespons rotasi layar secara otomatis. Namun, dalam arsitektur cloud phone, orientasi layar harus dikendalikan oleh status perangkat virtual di cloud, bukan oleh orientasi perangkat fisik lokal. Jika rotasi sistem otomatis diaktifkan secara langsung, masalah berikut dapat muncul:

  • Rotasi lokal tidak tersinkronisasi dengan status cloud, sehingga menyebabkan tampilan layar yang salah.

  • Pengguna yang secara tidak sengaja memutar ponselnya dapat memicu rotasi yang mengganggu operasi normal.

  • Lapisan rendering video, seperti SurfaceView, TextureView, atau Metal/OpenGL View, tidak menyesuaikan diri dengan orientasi baru, sehingga gambar menjadi terpotong atau terdistorsi.

Oleh karena itu, Anda harus mengambil kendali atas rotasi: cloud mengirimkan instruksi orientasi tertentu, dan klien lokal kemudian memaksa pergantian orientasi antarmuka serta menyesuaikan logika rendering video secara tepat.

Ikhtisar Solusi

Solusi ini mengimplementasikan kemampuan inti berikut pada platform Android dan iOS:

  • Mekanisme terpadu untuk menerima instruksi rotasi

    • Menerima perintah rotasi dari cloud melalui channel data kustom, seperti wy_vdagent_default_dc.

    • Menggunakan protokol standar untuk mengurai parameter rotasi. Misalnya, rotation = 0/1/3 masing-masing merepresentasikan portrait, landscape left, dan landscape right.

  • Sinkronisasi paksa orientasi antarmuka lokal

    • Android: Memanggil setRequestedOrientation() untuk mengunci orientasi Activity secara dinamis.

    • iOS: Meng-override supportedInterfaceOrientations dan menggunakan setNeedsUpdateOfSupportedInterfaceOrientations (iOS 16+) atau API privat untuk mengganti orientasi.

    • Kedua platform menonaktifkan rotasi otomatis perangkat (shouldAutorotate = false atau menonaktifkan respons sensor) untuk memastikan respons hanya terjadi berdasarkan instruksi dari cloud.

  • Adaptasi presisi pada lapisan rendering video

    • Android: Menggunakan TextureView dan memanggil setSurfaceRotation() untuk memutar tekstur dasar secara langsung, sehingga menghindari distorsi citra.

    • iOS: Memutar StreamView menggunakan CGAffineTransform dan menyesuaikan pusatnya secara dinamis agar citra tetap terpusat dan memiliki rasio aspek yang benar.

  • Dukungan untuk multiple orientasi

    • Mendukung tiga orientasi: Portrait, Landscape Left, dan Landscape Right.

    • Menangani perbedaan posisi tombol Home secara tepat untuk meningkatkan konsistensi interaksi pada perangkat iOS dalam mode landscape.

Langkah Implementasi

Menangani rotasi lokal di SDK Android

// Nonaktifkan rotasi otomatis untuk cloud phone.
bundle.putBoolean(StreamView.CONFIG_DISABLE_ORIENTATION_CLOUD_CONTROL, true);
// Gunakan TextureView.
mStreamView = findViewById(R.id.stream_view);
mStreamView.enableTextureView(true);
// Tangani rotasi.
mStreamView.getASPEngineDelegate().addDataChannel(new DataChannel("wy_vdagent_default_dc") {
            @Override
            protected void onReceiveData(byte[] buf) {
                String str = "";
                try {
                    str = new String(buf, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    str = new String(buf);
                }
                Log.i(TAG, "wy_vdagent_default_dc dc received " + buf.length + " bytes data:" + str);
                CommandUtils.parseCommand(str, new CommandUtils.CommandListener() {
                    @Override
                    public void onCameraAuthorize() {
                        checkStartCpd();
                    }
                    @Override
                    public void onRotation(int rotation) {
                        runOnUiThread(() -> {
                            mStreamView.setSurfaceRotation(rotation);
                            if (rotation == 1) {
                                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                            } else if (rotation == 3) {
                                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
                            } else {
                                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                            }
                        });
                    }
                    @Override
                    public void onUnknownCommand(String cmd) {
                        showError("Unknown command: " + cmd);
                    }
                });
            }
            @Override
            protected void onConnectStateChanged(DataChannelConnectState state) {
                Log.i(TAG, "wy_vdagent_default_dc dc connection state changed to " + state);
            }
        });

Menangani rotasi lokal di SDK iOS

  1. Dalam list.info, konfigurasikan Supported interface orientations agar aplikasi mendukung orientasi portrait, landscape left, dan landscape right.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <array>
    	<string>UIInterfaceOrientationPortrait</string>
    	<string>UIInterfaceOrientationLandscapeLeft</string>
    	<string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    </plist>
    
  2. Buat BaseViewController untuk mengimplementasikan logika rotasi.

    @implementation BaseViewController {
        NSInteger mRoration;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
    }
    
    - (void)switchRoration:(NSInteger)roration {
        mRoration = roration;
        // Paksa rotasi ke landscape (disarankan untuk iOS 16+).
        if (@available(iOS 16.0, *)) {
            [self setNeedsUpdateOfSupportedInterfaceOrientations];
        } else {
            // Metode lama (deprecated, tetapi kompatibel).
            NSNumber *value = @([self supportedInterfaceOrientations]);
            [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
        }
    }
    
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        switch (mRoration) {
            case 1:
                return UIInterfaceOrientationMaskLandscapeLeft;
            case 3:
                return UIInterfaceOrientationMaskLandscapeRight;
            default:
                return UIInterfaceOrientationMaskPortrait;
        }
    }
    
    - (BOOL)shouldAutorotate {
        return false; // Izinkan rotasi otomatis hanya ke orientasi yang didukung.
    }
    
    // Opsional: Tentukan orientasi preferensi saat presentasi.
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
        switch (mRoration) {
            case 1:  return UIInterfaceOrientationLandscapeLeft;
            case 3:  return UIInterfaceOrientationLandscapeRight;
            default: return UIInterfaceOrientationPortrait;
        }
    }
    
    @end
  3. Buat jalur agar instruksi dari cloud dapat mencapai UI lokal dan lapisan rendering.

    self.esdAgent = [[DemoEDSAgentChannel alloc] initWithParameter:DATA_CHANNEL_NAME];
    self.esdAgent.streamView = self.streamView;
    self.esdAgent.viewController = self;
    [self.streamView addDataChannel:self.esdAgent];
  4. Pantau dan respons instruksi rotasi cloud phone.

    @interface DemoEDSAgentChannel() <CommandListener>
    
    @property (nonatomic, assign) CGRect rect;
    
    @end
    
    @implementation DemoEDSAgentChannel
    
    - (void)setViewController:(BaseViewController *)viewController {
        self.rect = viewController.view.bounds;
        _viewController = viewController;
    }
    
    - (void)onConnectStateChanged:(ASPDCConnectState)state {
        NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %ld", state);
        if (state == OPEN) {
            // untuk mengirim data
        }
    }
        
    - (void)onReceiveData:(NSData * _Nonnull)buf {
        NSString *string = [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
        NSLog(@"[DemoEDSAgentChannel] onConnectStateChanged %@", string);
        [CommandUtils parseCommand:string listener:self];
    }
    
    #pragma mark - CommandListener
    - (void)onRotation:(NSInteger)value {
        NSLog(@"[DemoEDSAgentChannel] onRotation %ld", value);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.viewController switchRoration:value];
            if (value == 1 || value == 3) {
                self.streamView.center = CGPointMake(CGRectGetMidY(self.viewController.view.bounds),
                                                     CGRectGetMidX(self.viewController.view.bounds));
                self.streamView.transform = CGAffineTransformMakeRotation(-M_PI_2*value) ;
            } else {
                self.streamView.center = CGPointMake(CGRectGetMidX(self.rect),
                                                     CGRectGetMidY(self.rect));
                self.streamView.transform = CGAffineTransformIdentity;
            }
        });
    }