diff --git a/example/ios/Podfile b/example/ios/Podfile index 9411102..fab83c6 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -37,5 +37,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0' + end end end diff --git a/example/lib/main.dart b/example/lib/main.dart index 1784065..d64a2f3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -12,8 +12,8 @@ import 'privacy.dart'; void main() { runApp(MaterialApp( - debugShowCheckedModeBanner: false, - home: Platform.isAndroid? Privacy() : Home(), + debugShowCheckedModeBanner: false, + home: Platform.isAndroid ? Privacy() : Home(), )); } @@ -34,7 +34,7 @@ class _HomeState extends State { bool _isPublish = false; bool _isPublishing = false; // The controller for publisher. - camera.CameraController _cameraController = null; + camera.CameraController _cameraController; @override Widget build(BuildContext context) { @@ -64,7 +64,9 @@ class _HomeState extends State { _urlController.addListener(_onUserEditUrl); PackageInfo.fromPlatform().then((info) { - setState(() { _info = info; }); + setState(() { + _info = info; + }); }).catchError((e) { print('Platform error $e'); }); @@ -100,7 +102,7 @@ class _HomeState extends State { return _url != null && _url.contains('://'); } - void disposeCamera() async { + Future disposeCamera() async { if (_cameraController == null) { return; } @@ -113,8 +115,8 @@ class _HomeState extends State { void stopPublish() async { await disposeCamera(); - setState(() { }); - print('Stop publish url=$_url, publishing=$_isPublishing, controller=${_cameraController?.value.isInitialized}'); + setState(() {}); + print('Stop publish url=$_url, publishing=$_isPublishing, controller=${_cameraController.value.isInitialized}'); } void _onStartPlayOrPublish(BuildContext context) async { @@ -123,12 +125,12 @@ class _HomeState extends State { return; } - print('${_isPublishing? "Stop":""} ${_isPublish? "Publish":"Play"} url=$_url, publishing=$_isPublishing'); + print('${_isPublishing ? "Stop" : ""} ${_isPublish ? "Publish" : "Play"} url=$_url, publishing=$_isPublishing'); // For player. if (!_isPublish) { Navigator.push(context, MaterialPageRoute(builder: (context) { - return _url.startsWith('webrtc://')? WebRTCStreamingPlayer(_url) : LiveStreamingPlayer(_url); + return _url.startsWith('webrtc://') ? WebRTCStreamingPlayer(_url) : LiveStreamingPlayer(_url); })); return; } @@ -160,7 +162,9 @@ class _HomeState extends State { _cameraController = camera.CameraController(desc, camera.ResolutionPreset.low); _cameraController.addListener(() { - setState(() { print('got camera event'); }); + setState(() { + print('got camera event'); + }); }); await _cameraController.initialize(); @@ -169,7 +173,9 @@ class _HomeState extends State { await _cameraController.startVideoStreaming(_url, bitrate: 300 * 1000); print('Start streaming to $_url'); - setState(() { _isPublishing = true; }); + setState(() { + _isPublishing = true; + }); } } @@ -177,7 +183,9 @@ class _HomeState extends State { if (!v) { stopPublish(); } - setState(() { _isPublish = v; }); + setState(() { + _isPublish = v; + }); } } @@ -189,9 +197,9 @@ class UrlInputDisplay extends StatelessWidget { Widget build(BuildContext context) { return Container( child: TextField( - controller: _controller, autofocus: false, - decoration: InputDecoration(hintText: 'Please select or input url...') - ), + controller: _controller, + autofocus: false, + decoration: InputDecoration(hintText: 'Please select or input url...')), padding: EdgeInsets.fromLTRB(5, 0, 5, 0), ); } @@ -205,80 +213,94 @@ class DemoUrlsDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(child: - _isPublish? Column(children: [ - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp_publish, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp_publish, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp_publish2, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish2), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp_publish2, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ],) : Column(children: [ - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtmp, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtmp, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HLS', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.hls, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hls), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.hls, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTP-FLV', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.flv, style: TextStyle(color: Colors.grey[500])), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flv), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.flv, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('WebRTC', style: TextStyle(fontWeight: FontWeight.bold)), - Text(flutter_live.FlutterLive.rtc, style: TextStyle(color: Colors.grey[500], fontSize: 15)), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtc), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.rtc, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTPS-FLV', style: TextStyle(fontWeight: FontWeight.bold)), - Container( - child: Text(flutter_live.FlutterLive.flvs, style: TextStyle(color: Colors.grey[500], fontSize: 15)), - padding: EdgeInsets.only(top: 3, bottom:3), - ), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flvs), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.flvs, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ListTile( - title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('HTTPS-HLS', style: TextStyle(fontWeight: FontWeight.bold)), - Container( - child: Text(flutter_live.FlutterLive.hlss, style: TextStyle(color: Colors.grey[500], fontSize: 14)), - padding: EdgeInsets.only(top: 3, bottom: 3), - ), - ]), - onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hlss), contentPadding: EdgeInsets.zero, - leading: Radio(value: flutter_live.FlutterLive.hlss, groupValue: _url, onChanged: _onUserSelectUrl), - ), - ]), + return Container( + child: _isPublish + ? Column( + children: [ + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp_publish, style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtmp_publish, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp_publish2, style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp_publish2), + contentPadding: EdgeInsets.zero, + leading: Radio( + value: flutter_live.FlutterLive.rtmp_publish2, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ], + ) + : Column(children: [ + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('RTMP', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtmp, style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtmp), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.rtmp, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('HLS', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.hls, style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hls), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.hls, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('HTTP-FLV', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.flv, style: TextStyle(color: Colors.grey[500])), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flv), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.flv, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('WebRTC', style: TextStyle(fontWeight: FontWeight.bold)), + Text(flutter_live.FlutterLive.rtc, style: TextStyle(color: Colors.grey[500], fontSize: 15)), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.rtc), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.rtc, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('HTTPS-FLV', style: TextStyle(fontWeight: FontWeight.bold)), + Container( + child: Text(flutter_live.FlutterLive.flvs, style: TextStyle(color: Colors.grey[500], fontSize: 15)), + padding: EdgeInsets.only(top: 3, bottom: 3), + ), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.flvs), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.flvs, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ListTile( + title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('HTTPS-HLS', style: TextStyle(fontWeight: FontWeight.bold)), + Container( + child: Text(flutter_live.FlutterLive.hlss, style: TextStyle(color: Colors.grey[500], fontSize: 14)), + padding: EdgeInsets.only(top: 3, bottom: 3), + ), + ]), + onTap: () => _onUserSelectUrl(flutter_live.FlutterLive.hlss), + contentPadding: EdgeInsets.zero, + leading: Radio(value: flutter_live.FlutterLive.hlss, groupValue: _url, onChanged: _onUserSelectUrl), + ), + ]), ); } } @@ -289,7 +311,8 @@ class ControlDisplay extends StatelessWidget { final bool _isPubslish; final bool _isPublishing; final ValueChanged _onSwitchPublish; - ControlDisplay(this._urlAvailable, this._onStartPlayOrPublish, this._isPubslish, this._isPublishing, this._onSwitchPublish); + ControlDisplay( + this._urlAvailable, this._onStartPlayOrPublish, this._isPubslish, this._isPublishing, this._onSwitchPublish); @override Widget build(BuildContext context) { @@ -297,7 +320,7 @@ class ControlDisplay extends StatelessWidget { if (_isPublishing) { return 'Stop'; } - return _isPubslish? 'Publish' : 'Play'; + return _isPubslish ? 'Publish' : 'Play'; }; return Row( @@ -311,9 +334,9 @@ class ControlDisplay extends StatelessWidget { ), Container( width: 120, - child:ElevatedButton( + child: ElevatedButton( child: Text(actionText()), - onPressed: _urlAvailable? _onStartPlayOrPublish : null, + onPressed: _urlAvailable ? _onStartPlayOrPublish : null, ), ), ], @@ -337,15 +360,16 @@ class CameraDisplay extends StatelessWidget { } if (!_cameraController.value.isInitialized) { - return Container(child: Center(child: Text( - 'Camera not available', style: TextStyle(color: Colors.red[500]), + return Container( + child: Center( + child: Text( + 'Camera not available', + style: TextStyle(color: Colors.red[500]), ))); } return AspectRatio( - aspectRatio: _cameraController.value.aspectRatio, - child: camera.CameraPreview(_cameraController) - ); + aspectRatio: _cameraController.value.aspectRatio, child: camera.CameraPreview(_cameraController)); } } @@ -361,10 +385,11 @@ class PlatformDisplay extends StatelessWidget { Text.rich( TextSpan( text: 'SRS/v${_info.version}+${_info.buildNumber}', - recognizer: TapGestureRecognizer() ..onTap = () async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setBool("login", false); - }, + recognizer: TapGestureRecognizer() + ..onTap = () async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setBool("login", false); + }, ), ), ], @@ -388,9 +413,7 @@ class _LiveStreamingPlayerState extends State { return Scaffold( appBar: AppBar(title: Text('SRS Live Streaming')), body: fijkplayer.FijkView( - player: _player.fijk, panelBuilder: fijkplayer.fijkPanel2Builder(), - fsFit: fijkplayer.FijkFit.fill - ), + player: _player.fijk, panelBuilder: fijkplayer.fijkPanel2Builder(), fsFit: fijkplayer.FijkFit.fill), ); } @@ -430,9 +453,9 @@ class _WebRTCStreamingPlayerState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('SRS WebRTC Streaming')), - body: GestureDetector(onTap: _switchLoudspeaker, child: Container( - child: webrtc.RTCVideoView(_video), decoration: BoxDecoration(color: Colors.grey[500]) - )), + body: GestureDetector( + onTap: _switchLoudspeaker, + child: Container(child: webrtc.RTCVideoView(_video), decoration: BoxDecoration(color: Colors.grey[500]))), ); } @@ -449,7 +472,9 @@ class _WebRTCStreamingPlayerState extends State { // Render stream when got remote stream. _player.onRemoteStream = (webrtc.MediaStream stream) { // @remark It's very important to use setState to set the srcObject and notify render. - setState(() { _video.srcObject = stream; }); + setState(() { + _video.srcObject = stream; + }); }; // Auto start play WebRTC streaming. @@ -457,7 +482,7 @@ class _WebRTCStreamingPlayerState extends State { } void _switchLoudspeaker() { - print('setSpeakerphoneOn: $_loudspeaker(${_loudspeaker? "Loudspeaker":"Earpiece"})'); + print('setSpeakerphoneOn: $_loudspeaker(${_loudspeaker ? "Loudspeaker" : "Earpiece"})'); flutter_live.FlutterLive.setSpeakerphoneOn(_loudspeaker); _loudspeaker = !_loudspeaker; } @@ -469,4 +494,3 @@ class _WebRTCStreamingPlayerState extends State { _player.dispose(); } } - diff --git a/lib/flutter_live.dart b/lib/flutter_live.dart index 6cd7607..ab3133a 100644 --- a/lib/flutter_live.dart +++ b/lib/flutter_live.dart @@ -110,9 +110,10 @@ class RealtimePlayer { /// streamUrl: "webrtc://d.ossrs.net:11985/live/livestream" class WebRTCUri { /// The api server url for WebRTC streaming. - String api; + String api = ""; + /// The stream url to play or publish. - String streamUrl; + String streamUrl = ""; /// Parse the url to WebRTC uri. static WebRTCUri parse(String url) { @@ -120,21 +121,21 @@ class WebRTCUri { var schema = 'https'; // For native, default to HTTPS if (uri.queryParameters.containsKey('schema')) { - schema = uri.queryParameters['schema']; + schema = uri.queryParameters['schema']!; } else { schema = 'https'; } - var port = (uri.port > 0)? uri.port : 443; + var port = (uri.port > 0) ? uri.port : 443; if (schema == 'https') { - port = (uri.port > 0)? uri.port : 443; + port = (uri.port > 0) ? uri.port : 443; } else if (schema == 'http') { - port = (uri.port > 0)? uri.port : 1985; + port = (uri.port > 0) ? uri.port : 1985; } var api = '/rtc/v1/play/'; if (uri.queryParameters.containsKey('play')) { - api = uri.queryParameters['play']; + api = uri.queryParameters['play']!; } var apiParams = []; @@ -145,7 +146,7 @@ class WebRTCUri { } var apiUrl = '${schema}://${uri.host}:${port}${api}'; - if (!apiParams.isEmpty) { + if (apiParams.isEmpty) { apiUrl += '?' + apiParams.join('&'); } @@ -159,8 +160,8 @@ class WebRTCUri { /// A WebRTC player, using [flutter_webrtc](https://pub.dev/packages/flutter_webrtc) class WebRTCPlayer { - webrtc.AddStreamCallback _onRemoteStream; - webrtc.RTCPeerConnection _pc; + webrtc.AddStreamCallback? _onRemoteStream; + webrtc.RTCPeerConnection? _pc; /// When got a remote stream. set onRemoteStream(webrtc.AddStreamCallback v) { @@ -168,14 +169,13 @@ class WebRTCPlayer { } /// Initialize the player. - void initState() { - } + void initState() {} /// Start play a url. /// [url] must a path parsed by [WebRTCUri.parse] in https://github.com/rtcdn/rtcdn-draft Future play(String url) async { if (_pc != null) { - await _pc.close(); + await _pc!.close(); } // Create the peer connection. @@ -187,36 +187,36 @@ class WebRTCPlayer { print('WebRTC: createPeerConnection done'); // Setup the peer connection. - _pc.onAddStream = (webrtc.MediaStream stream) { + _pc!.onAddStream = (webrtc.MediaStream stream) { print('WebRTC: got stream ${stream.id}'); if (_onRemoteStream == null) { print('Warning: Stream ${stream.id} is leak'); return; } - _onRemoteStream(stream); + _onRemoteStream!(stream); }; - _pc.addTransceiver( - kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, - init: webrtc.RTCRtpTransceiverInit(direction: webrtc.TransceiverDirection.RecvOnly), + _pc!.addTransceiver( + kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeAudio, + init: webrtc.RTCRtpTransceiverInit(direction: webrtc.TransceiverDirection.RecvOnly), ); - _pc.addTransceiver( + _pc!.addTransceiver( kind: webrtc.RTCRtpMediaType.RTCRtpMediaTypeVideo, init: webrtc.RTCRtpTransceiverInit(direction: webrtc.TransceiverDirection.RecvOnly), ); print('WebRTC: Setup PC done, A|V RecvOnly'); // Start SDP handshake. - webrtc.RTCSessionDescription offer = await _pc.createOffer({ + webrtc.RTCSessionDescription offer = await _pc!.createOffer({ 'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true}, }); - await _pc.setLocalDescription(offer); - print('WebRTC: createOffer, ${offer.type} is ${offer.sdp.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); + await _pc!.setLocalDescription(offer); + print('WebRTC: createOffer, ${offer.type} is ${offer.sdp?.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); - webrtc.RTCSessionDescription answer = await _handshake(url, offer.sdp); - print('WebRTC: got ${answer.type} is ${answer.sdp.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); + webrtc.RTCSessionDescription answer = await _handshake(url, offer.sdp ?? ""); + print('WebRTC: got ${answer.type} is ${answer.sdp?.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}'); - await _pc.setRemoteDescription(answer); + await _pc!.setRemoteDescription(answer); } /// Handshake to exchange SDP, send offer and got answer. @@ -261,8 +261,7 @@ class WebRTCPlayer { /// Dispose the player. void dispose() { if (_pc != null) { - _pc.close(); + _pc!.close(); } } } - diff --git a/pubspec.yaml b/pubspec.yaml index 4d7c961..9345643 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,17 +2,28 @@ name: flutter_live version: 1.0.17 description: Live streaming player, iOS+Android, RTMP/HTTP-FLV/HLS/WebRTC, by Flutter+SRS. homepage: https://github.com/ossrs/flutter_live +publish_to: 'none' environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.20.0 <2.0.0" + sdk: ">=2.13.0 <3.0.0" dependencies: flutter: sdk: flutter - fijkplayer: ^0.8.8 - flutter_webrtc: ^0.6.3 - camera_with_rtmp: ^0.3.2 + fijkplayer: ^0.10.0 + flutter_webrtc: ^0.6.5 + rtmp_publisher: + git: + url: git@github.com:hetao29/flutter_rtmp_publisher.git + ref: 172c092a9d23aab1d873a67963d625a98ab7c21f + #camera_with_rtmp: + #path: ../flutter_rtmppublisher_hetao29 + # git: + # url: git@github.com:babulpatel1309/flutter_rtmppublisher.git + + #git: + # url: https://github.com/hetao29/flutter_rtmppublisher.git + # ref: 79010b3724574f79b60974b64982aad751b56803 dev_dependencies: flutter_test: