Todos os produtos
Search
Central de documentação

Cloud Phone:Rotacionar a tela do cloud phone localmente usando o SDK

Última atualização: Jun 28, 2026

Em um ambiente de cloud phone, o dispositivo virtual na nuvem controla a orientação da tela, e não os sensores do dispositivo local. Quando a nuvem alterna de retrato para paisagem, o cliente local deve acompanhar essa mudança de forma sincronizada. Sem essa sincronização, o fluxo de vídeo pode aparecer cortado, esticado ou rotacionado incorretamente, e as coordenadas de toque deixam de corresponder às regiões corretas da tela.

Este tópico explica como implementar a rotação de tela controlada pela nuvem no Android e iOS usando o ECP SDK.

Como funciona

A sincronização da rotação da tela exige o tratamento de dois aspectos independentes:

  1. Orientação da UI: force a Activity local (Android) ou o ViewController (iOS) a alternar a orientação para corresponder ao dispositivo na nuvem.

  2. Rotação do quadro de vídeo: ajuste a camada de renderização para que o fluxo de vídeo preencha a tela corretamente, sem distorções.

Ambos os processos devem ocorrer simultaneamente. Se você atualizar a orientação da UI sem rotacionar o quadro de vídeo (ou vice-versa), a exibição ficará desalinhada ou distorcida.

A nuvem envia um valor de rotação por meio de um canal de dados chamado wy_vdagent_default_dc. O cliente local recebe esse valor, alterna a orientação da UI e rotaciona a camada de renderização adequadamente.

Valor de rotação

Orientação

0

Retrato

1

Paisagem esquerda

3

Paisagem direita

O sistema analisa os comandos usando CommandUtils.parseCommand(), que aciona um callback onRotation com o valor de rotação.

Pré-requisitos

Antes de começar, verifique se você possui:

  • O ECP SDK para Android ou iOS integrado ao seu projeto.

  • Uma instância de cloud phone em execução e acessível a partir do seu cliente local.

Gerenciar a rotação local no Android

A implementação para Android utiliza TextureView para renderização e um DataChannel para receber comandos de rotação da nuvem.

Etapa 1: Configurar o StreamView

Desative o controle automático de orientação na nuvem e alterne a visualização de renderização para TextureView:

// Disable cloud-side automatic orientation control
bundle.putBoolean(StreamView.CONFIG_DISABLE_ORIENTATION_CLOUD_CONTROL, true);

// Use TextureView instead of SurfaceView for rotation support
mStreamView = findViewById(R.id.stream_view);
mStreamView.enableTextureView(true);

Etapa 2: Receber comandos de rotação e sincronizar a orientação

Registre um listener de DataChannel em wy_vdagent_default_dc. Quando onRotation é disparado, o handler executa duas ações na thread da UI:

  • Etapa 2a — Rotacionar o quadro de vídeo: chame setSurfaceRotation() para girar a textura subjacente e corresponder à orientação da nuvem.

  • Etapa 2b — Bloquear a orientação da Activity: chame setRequestedOrientation() para impedir que a rotação baseada em sensores substitua o estado definido pela nuvem.

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(() -> {
                    // Step 2a: Rotate the video texture to match the cloud orientation
                    mStreamView.setSurfaceRotation(rotation);
                    // Step 2b: Lock the Activity orientation to prevent sensor-driven 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);
    }
});

Tanto setSurfaceRotation() quanto setRequestedOrientation() são executados na thread da UI por meio de runOnUiThread().

Gerenciar a rotação local no iOS

A implementação para iOS divide o trabalho entre duas classes: BaseViewController gerencia o estado de orientação, enquanto DemoEDSAgentChannel recebe os comandos da nuvem e aplica a transformação de rotação ao StreamView.

Etapa 1: Configurar as orientações suportadas no Info.plist

No arquivo Info.plist, declare suporte para retrato, paisagem esquerda e paisagem direita:

<?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>

Etapa 2: Implementar o BaseViewController

O BaseViewController armazena o estado atual de rotação e força a alteração de orientação quando a nuvem envia um novo valor. Definir shouldAutorotate como false desativa a rotação baseada em sensores. Assim, apenas os comandos da nuvem controlarão as mudanças de orientação.

@implementation BaseViewController {
    NSInteger mRoration;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

- (void)switchRoration:(NSInteger)roration {
    mRoration = roration;
    // Force orientation switch (recommended for iOS 16+)
    if (@available(iOS 16.0, *)) {
        [self setNeedsUpdateOfSupportedInterfaceOrientations];
    } else {
        // Legacy method (deprecated, but compatible with iOS 15 and earlier)
        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; // Disable sensor-driven rotation; only cloud commands control orientation
}

// Optional: Specify the preferred orientation for presentation
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    switch (mRoration) {
        case 1:  return UIInterfaceOrientationLandscapeLeft;
        case 3:  return UIInterfaceOrientationLandscapeRight;
        default: return UIInterfaceOrientationPortrait;
    }
}

@end

Etapa 3: Configurar o canal de dados

No seu view controller, inicialize o DemoEDSAgentChannel e adicione-o ao StreamView:

self.esdAgent = [[DemoEDSAgentChannel alloc] initWithParameter:DATA_CHANNEL_NAME];
self.esdAgent.streamView = self.streamView;
self.esdAgent.viewController = self;
[self.streamView addDataChannel:self.esdAgent];

Etapa 4: Processar comandos de rotação no DemoEDSAgentChannel

O DemoEDSAgentChannel decodifica os dados recebidos, analisa o comando de rotação e aplica duas alterações na thread principal:

  • Etapa 4a — Alternar a orientação da UI: chame switchRoration: no view controller.

  • Etapa 4b — Rotacionar o quadro de vídeo: aplique uma CGAffineTransform ao StreamView.

Para orientações em paisagem, as coordenadas centrais do StreamView (X e Y) são trocadas para manter o vídeo centralizado após a transformação de 90 graus.

@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) {
        // to send 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(), ^{
        // Step 4a: Switch UI orientation
        [self.viewController switchRoration:value];
        // Step 4b: Rotate the StreamView to match the video frame orientation
        if (value == 1 || value == 3) {
            // Swap X and Y center coordinates to keep the view centered in landscape
            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;
        }
    });
}

@end