HaishinKit.dart icon indicating copy to clipboard operation
HaishinKit.dart copied to clipboard

StreamTextureView not showing camera preview on iOS

Open dylandamsma opened this issue 7 months ago • 1 comments

Describe the bug

The StreamViewTexture widget is not returning a preview on iOS. The RtmpStream#registerTexture method in RTMPStreamHandler.swift seems to be incorrect.

To Reproduce

  1. Use StreamViewTexture as per example
  2. attachVideo as per example
  3. See no preview of camera
  4. Start streaming (works)

Expected behavior

  1. StreamViewTexture(_stream) should return the preview from camera on Flutter

Version

Latest from Main

Smartphone info.

  • iOS 18 (iPhone 15 pro max)

Additional context

import 'dart:async';

import 'package:audio_session/audio_session.dart';
import 'package:baseline/resources/constants/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:haishin_kit/video_settings.dart';
import 'package:keep_screen_on/keep_screen_on.dart';
import 'package:nylo_framework/nylo_framework.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:haishin_kit/audio_source.dart';
import 'package:haishin_kit/stream_view_texture.dart';
import 'package:haishin_kit/rtmp_connection.dart';
import 'package:haishin_kit/rtmp_stream.dart';
import 'package:haishin_kit/video_source.dart';

class HaishinInterfacePage extends NyStatefulWidget {
  static const path = '/stream-interface';

  HaishinInterfacePage({super.key})
      : super(path, child: () => _HaishinInterfacePageState());
}

class _HaishinInterfacePageState extends NyState<HaishinInterfacePage> {
  RtmpConnection? _connection;
  RtmpStream? _stream;
  bool _recording = false;
  CameraPosition currentPosition = CameraPosition.back;

  @override
  void initState() {
    super.initState();
    NyLogger.info("HaishinInterfacePage initState");

    KeepScreenOn.turnOn();

    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeRight,
      DeviceOrientation.landscapeLeft,
    ]);

    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);

    setupStream();
  }

  void setupStream() async {
    try {
      await Permission.camera.request();
      await Permission.microphone.request();

      final session = await AudioSession.instance;
      await session.configure(const AudioSessionConfiguration(
        avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
        avAudioSessionCategoryOptions:
            AVAudioSessionCategoryOptions.allowBluetooth,
      ));

      RtmpConnection connection = await RtmpConnection.create();
      connection.eventChannel.receiveBroadcastStream().listen((event) {
        NyLogger.info('Event received: $event');
        if (event != null) {
          var data = event["data"];
          if (data != null) {
            NyLogger.info('Event data: $data');
            var code = data["code"];
            if (code != null) {
              NyLogger.info('Event code: $code');
              switch (code) {
                case 'NetConnection.Connect.Success':
                  _stream?.videoSettings.width = 1920;
                  _stream?.videoSettings.height = 1080;
                  _stream?.videoSettings.bitrate = 4000 * 1000; // Bitrate HD
                  _stream?.videoSettings.profileLevel = ProfileLevel.h264High52;
                  _stream?.videoSettings.frameInterval = 1;
                  _stream?.publish('8d63-yapg-0h78-rhxf-e3u5');
                  setState(() {
                    _recording = true;
                  });
                  break;
              }
            } else {
              NyLogger.error('Code is null in event data');
            }
          } else {
            NyLogger.error('Event data is null');
          }
        } else {
          NyLogger.error('Event is null');
        }
      });

      RtmpStream stream = await RtmpStream.create(connection);
      stream.attachAudio(AudioSource());
      stream.attachVideo(VideoSource(position: currentPosition));

      if (!mounted) return;

      setState(() {
        NyLogger.info('Connection created');
        _connection = connection;
        _stream = stream;
      });
    } catch (e) {
      NyLogger.error("Initialization error: $e");
    }
  }

  @override
  void dispose() {
    _stopStream();
    KeepScreenOn.turnOff();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
    super.dispose();
  }

  void _startStream() async {
    if (_recording) {
      _connection?.close();
      setState(() {
        _recording = false;
      });
    } else {
      _connection?.connect("rtmp://a.rtmp.youtube.com/live2");
    }
  }

  void _stopStream() async {
    _connection?.close();
  }

  @override
  Widget view(BuildContext context) {
    return Scaffold(
      backgroundColor: BColors.gray900,
      body: Stack(
        children: [
          SizedBox(
            width: double.infinity,
            height: double.infinity,
            child: StreamViewTexture(_stream),
          ),
          Positioned(
            left: 36,
            right: 36,
            top: 24,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Flexible(
                  child: Image.asset(
                    'public/assets/images/baseline-light.png',
                    width: 120,
                  ),
                ),
                ElevatedButton(
                  onPressed: () async {
                    NyLogger.info('Pressed Start Stream');
                    _startStream();
                  },
                  style: ElevatedButton.styleFrom(
                    elevation: 0,
                    padding:
                        const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(4),
                    ),
                  ),
                  child: const Text(
                    "Toggle Stream",
                    style: TextStyle(color: Colors.white, fontSize: 16),
                    textAlign: TextAlign.center,
                  ),
                ),
              ],
            ),
          ),
          Positioned(
            left: 36,
            right: 36,
            bottom: 24,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Flexible(
                  child: Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    decoration: BoxDecoration(
                      color: Colors.black.withOpacity(0.5),
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: const Text(
                      "Score will be displayed here on stream",
                      style: TextStyle(color: Colors.white, fontSize: 16),
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    // Add your instructions logic here
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: BColors.gray700,
                    elevation: 0,
                    padding:
                        const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(4),
                    ),
                  ),
                  child: const Text(
                    "Instructions",
                    style: TextStyle(color: Colors.white, fontSize: 16),
                    textAlign: TextAlign.center,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Screenshots

image image

Relevant log output

No response

dylandamsma avatar Jul 19 '24 09:07 dylandamsma