女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何快速構(gòu)建一個移動跨平臺視頻通話應(yīng)用

BYXG_shengwang ? 來源:xx ? 2019-02-24 06:01 ? 次閱讀

上周,我們發(fā)布了Agora Flutter SDK之后,吸引了 Flutter 社區(qū)的諸多關(guān)注。Google Flutter 與 Dart 的產(chǎn)品負(fù)責(zé)人 Tim Sneath,在看到 Agora Flutter SDK 后,還特意在社交媒體上發(fā)推點贊。

今天我們就來看一下如何使用 Agora Flutter SDK 快速構(gòu)建一個簡單的移動跨平臺視頻通話應(yīng)用。

環(huán)境準(zhǔn)備

在 Flutter 中文網(wǎng)(flutterchina.club)上,關(guān)于搭建開放環(huán)境的教程已經(jīng)相對比較完善了,有關(guān) IDE 與環(huán)境配置的過程本文不再贅述,若 Flutter 安裝有問題,可以執(zhí)行 flutter doctor 做配置檢查。

本文使用 MacOS 下的 VS Code 作為主開發(fā)環(huán)境。

目標(biāo)

我們希望可以使用 Flutter+Agora Flutter SDK 實現(xiàn)一個簡單的視頻通話應(yīng)用,這個視頻通話應(yīng)用需要包含以下功能,

加入通話房間

視頻通話

前后攝像頭切換

本地靜音/取消靜音

聲網(wǎng)的視頻通話是按通話房間區(qū)分的,同一個通話房間內(nèi)的用戶都可以互通。為了方便區(qū)分,這個演示會需要一個簡單的表單頁面讓用戶提交選擇加入哪一個房間。同時一個房間內(nèi)可以容納最多 4 個用戶,當(dāng)用戶數(shù)不同時我們需要展示不同的布局。

想清楚了?動手?jǐn)]代碼了。

項目創(chuàng)建

首先在 VS Code 選擇查看->命令面板(或直接使用 cmd + shift + P)調(diào)出命令面板,輸入 flutter 后選擇Flutter: New Project創(chuàng)建一個新的 Flutter 項目,項目的名字為agora_flutter_quickstart,隨后等待項目創(chuàng)建完成即可。

現(xiàn)在執(zhí)行啟動->啟動調(diào)試(或 F5)即可看到一個最簡單的計數(shù) App。

看起來我們有了一個很好的開始

接下去我們需要對我們新建的項目做一下簡單的配置以使其可以引用和使用 Agora Flutter SDK。

打開項目根目錄下的 pubspec.yaml 文件,在dependencies下添加agora_rtc_engine:^0.9.0,

dependencies:

flutter:

sdk: flutter

# The following adds the Cupertino Icons font to your application.

# Use with the CupertinoIcons class for iOS style icons.

cupertino_icons: ^0.1.2

# add agora rtc sdk

agora_rtc_engine: ^0.9.0

dev_dependencies:

flutter_test:

sdk: flutter

保存后 VS Code 會自動執(zhí)行flutter packages get更新依賴。

應(yīng)用首頁

在項目配置完成后,我們就可以開始開發(fā)了。首先我們需要創(chuàng)建一個頁面文件替換掉默認(rèn)示例代碼中的MyHomePage類。我們可以在lib/src下創(chuàng)建一個pages目錄,并創(chuàng)建一個index.dart文件。

如果你已經(jīng)完成了官方教程 Write your first Flutter app,那么以下代碼對你來說就應(yīng)該不難理解。

classIndexPageextendsStatefulWidget {

@override

State createState() {

returnnewIndexState();

}

}

classIndexStateextendsState {

@override

Widget build(BuildContext context) {

// UI

}

onJoin() {

//TODO

}

}

現(xiàn)在我們需要開始在build方法中構(gòu)造首頁的 UI。

按上圖分解 UI 后,我們可以將我們的首頁代碼修改如下

@override

Widget build(BuildContext context) {

returnScaffold(

appBar: AppBar(

title: Text('Agora Flutter QuickStart'),

),

body: Center(

child: Container(

padding: EdgeInsets.symmetric(horizontal: 20),

height: 400,

child: Column(

children: [

Row(children: []),

Row(children: [

Expanded(

child: TextField(

decoration: InputDecoration(

border: UnderlineInputBorder(

borderSide: BorderSide(width: 1)),

hintText: 'Channel name'),

))

]),

Padding(

padding: EdgeInsets.symmetric(vertical: 20),

child: Row(

children: [

Expanded(

child: RaisedButton(

onPressed: () => onJoin(),

child: Text("Join"),

color: Colors.blueAccent,

textColor: Colors.white,

),

)

],

))

],

)),

));

}

執(zhí)行 F5 啟動查看,應(yīng)該可以看到下圖

看起來不錯!但也只是看起來不錯。我們的UI現(xiàn)在只能看,還不能交互。我們希望可以基于現(xiàn)在的 UI 實現(xiàn)以下功能,

1. 為 Join 按鈕添加回調(diào)導(dǎo)航到通話頁面

2. 對頻道名做檢查,若嘗試加入頻道時頻道名為空,則在 TextField 上提示錯誤

TextField 輸入校驗

TextField 自身提供了一個decoration屬性,我們可以提供一個InputDecoration的對象來標(biāo)識 TextField 的裝飾樣式。InputDecoration里的errorText屬性非常適合在我們這里被拿來使用, 同時我們利用TextEditingController對象來記錄 TextField 的值,以判斷當(dāng)前是否應(yīng)該顯示錯誤。因此經(jīng)過簡單的修改后,我們的 TextField 代碼就變成了這樣:

final _channelController = TextEditingController();

/// if channel textfield is validated to have error

bool _validateError = false;

@override

void dispose() {

// dispose input controller

_channelController.dispose();

super.dispose();

}

@override

Widget build(BuildContext context) {

...

TextField(

controller: _channelController,

decoration: InputDecoration(

errorText: _validateError

? "Channel name is mandatory"

: null,

border: UnderlineInputBorder(

borderSide: BorderSide(width: 1)),

hintText: 'Channel name'),

))

...

}

onJoin() {

// update input validation

setState(() {

_channelController.text.isEmpty

? _validateError = true

: _validateError = false;

});

}

在點擊加入頻道按鈕的時候回觸發(fā)onJoin回調(diào),回調(diào)中會先通過setState更新 TextField 的狀態(tài)以做組件重繪。

注意: 不要忘了 overridedispose方法在這個組件的生命周期結(jié)束時釋放_controller。

前往通話頁面

到這里我們的首頁基本就算完成了,最后我們在onJoin中創(chuàng)建MaterialPageRoute將用戶導(dǎo)航到通話頁面,在這里我們將獲取的頻道名作為通話頁面構(gòu)造函數(shù)的參數(shù)傳遞到下一個頁面CallPage。

import'./call.dart';

classIndexStateextendsState {

...

onJoin() {

// update input validation

setState(() {

_channelController.text.isEmpty

? _validateError = true

: _validateError = false;

});

if (_channelController.text.isNotEmpty) {

// push video page with given channel name

Navigator.push(

context,

MaterialPageRoute(

builder: (context) => newCallPage(

channelName: _channelController.text,

)));

}

}

通話頁面

同樣在/lib/src/pages目錄下,我們需要新建一個call.dart文件,在這個文件里我們會實現(xiàn)我們最重要的實時視頻通話邏輯。首先還是需要創(chuàng)建我們的CallPage類。如果你還記得我們在IndexPage的實現(xiàn),CallPage會需要在構(gòu)造函數(shù)中帶入一個參數(shù)作為頻道名。

classCallPageextendsStatefulWidget {

/// non-modifiable channel name of the page

finalString channelName;

/// Creates a call page with given channel name.

constCallPage({Key key, this.channelName}) : super(key: key);

@override

_CallPageState createState() {

returnnew_CallPageState();

}

}

class_CallPageStateextendsState {

@override

Widget build(BuildContext context) {

returnScaffold(

appBar: AppBar(

title: Text(widget.channelName),

),

backgroundColor: Colors.black,

body: Center(

child: Stack(

children: [],

)));

}

}

這里需要注意的是,我們并不需要把參數(shù)在創(chuàng)建state實例的時候傳入,state可以直接訪問widget.channelName獲取到組件的屬性。

引入聲網(wǎng)SDK

因為我們在最開始已經(jīng)在pubspec.yaml中添加了agora_rtc_engine的依賴,因此我們現(xiàn)在可以直接通過以下方式引入聲網(wǎng) SDK。

import'package:agora_rtc_engine/agora_rtc_engine.dart';

引入后即可以使用創(chuàng)建聲網(wǎng)媒體引擎實例。在使用聲網(wǎng) SDK 進(jìn)行視頻通話之前,我們需要進(jìn)行以下初始化工作。初始化工作應(yīng)該在整個頁面生命周期中只做一次,因此這里我們需要 overrideinitState方法,在這個方法里做好初始化。

class_CallPageStateextendsState {

@override

void initState() {

super.initState();

initialize();

}

void initialize() {

_initAgoraRtcEngine();

_addAgoraEventHandlers();

}

/// Create agora sdk instance and initialze

void _initAgoraRtcEngine() {

AgoraRtcEngine.create(APP_ID);

AgoraRtcEngine.enableVideo();

}

/// Add agora event handlers

void _addAgoraEventHandlers() {

AgoraRtcEngine.onError = (int code) {

// sdk error

};

AgoraRtcEngine.onJoinChannelSuccess =

(String channel, int uid, int elapsed) {

// join channel success

};

AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

// there's a new user joining this channel

};

AgoraRtcEngine.onUserOffline = (int uid, int reason) {

// there's an existing user leaving this channel

};

}

}

注意:有關(guān)如何獲取聲網(wǎng) APP_ID,請訪問 docs.gora.io 參閱官方文檔。

在以上的代碼中我們主要創(chuàng)建了聲網(wǎng)的媒體 SDK 實例并監(jiān)聽了關(guān)鍵事件,接下去我們會開始做視頻流的處理。

在一般的視頻通話中,對于本地設(shè)備來說一共會有兩種視頻流,本地流與遠(yuǎn)端流 - 前者需要通過本地攝像頭采集渲染并發(fā)送出去,后者需要接收遠(yuǎn)端流的數(shù)據(jù)后渲染。現(xiàn)在我們需要動態(tài)地將最多4人的視頻流渲染到通話頁面。

我們會以大致這樣的結(jié)構(gòu)渲染通話頁面。

這里和首頁不同的是,放置通話操作按鈕的工具欄是覆蓋在視頻上的,因此這里我們會使用Stack組件來放置層疊組件。

為了更好地區(qū)分 UI 構(gòu)建,我們將視頻構(gòu)建與工具欄構(gòu)建分為兩個方法。

本地流創(chuàng)建與渲染

要渲染本地流,需要在初始化 SDK 完成后創(chuàng)建一個供視頻流渲染的容器,然后通過 SDK 將本地流渲染到對應(yīng)的容器上。聲網(wǎng) SDK 提供了createNativeView的方法以創(chuàng)建容器,在獲取到容器并且成功渲染到容器視圖上后,我們就可以利用SDK加入頻道與其他客戶端互通了。

void initialize() {

_initAgoraRtcEngine();

_addAgoraEventHandlers();

// use _addRenderView everytime a native video view is needed

_addRenderView(0, (viewId) {

// local view setup & preview

AgoraRtcEngine.setupLocalVideo(viewId, 1);

AgoraRtcEngine.startPreview();

// state can access widget directly

AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0);

});

}

/// Create a native view and add a new video session object

/// The native viewId can be used to set up local/remote view

void _addRenderView(int uid, Function(int viewId) finished) {

Widget view = AgoraRtcEngine.createNativeView(uid, (viewId) {

setState(() {

_getVideoSession(uid).viewId = viewId;

if (finished != null) {

finished(viewId);

}

});

});

VideoSession session = VideoSession(uid, view);

_sessions.add(session);

}

注意:代碼最后利用 uid 與容器信息創(chuàng)建了一個VideoSession對象并添加到_sessions中,這主要是為了視頻布局需要,這塊稍后會詳細(xì)觸及。

遠(yuǎn)端流監(jiān)聽與渲染

遠(yuǎn)端流的監(jiān)聽其實我們已經(jīng)在前面的初始化代碼中提及了,我們可以監(jiān)聽 SDK 提供的onUserJoined與onUserOffline回調(diào)來判斷是否有其他用戶進(jìn)出當(dāng)前頻道,若有新用戶加入頻道,就為他創(chuàng)建一個渲染容器并做對應(yīng)的渲染;若有用戶離開頻道,則去掉他的渲染容器。

AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {

setState(() {

_addRenderView(uid, (viewId) {

AgoraRtcEngine.setupRemoteVideo(viewId, 1, uid);

});

});

};

AgoraRtcEngine.onUserOffline = (int uid, int reason) {

setState(() {

_removeRenderView(uid);

});

};

/// Remove a native view and remove an existing video session object

void _removeRenderView(int uid) {

VideoSession session = _getVideoSession(uid);

if (session != null) {

_sessions.remove(session);

}

AgoraRtcEngine.removeNativeView(session.viewId);

}

注意:_sessions的作用是在本地保存一份當(dāng)前頻道內(nèi)的視頻流列表信息。因此在用戶加入的時候,需要創(chuàng)建對應(yīng)的VideoSession對象并添加到sessions,在用戶離開的時候,則需要刪除對應(yīng)的VideoSession實例。

視頻流布局

在有了_sessions數(shù)組,且每一個本地/遠(yuǎn)端流都有了一個對應(yīng)的原生渲染容器后,我們就可以開始對視頻流進(jìn)行布局了。

/// Helper function to get list of native views

List _getRenderViews() {

return _sessions.map((session) => session.view).toList();

}

/// Video view wrapper

Widget _videoView(view) {

returnExpanded(child: Container(child: view));

}

/// Video view row wrapper

Widget _expandedVideoRow(List views) {

List wrappedViews =

views.map((Widget view) => _videoView(view)).toList();

returnExpanded(

child: Row(

children: wrappedViews,

));

}

/// Video layout wrapper

Widget _viewRows() {

List views = _getRenderViews();

switch (views.length) {

case1:

returnContainer(

child: Column(

children: [_videoView(views[0])],

));

case2:

returnContainer(

child: Column(

children: [

_expandedVideoRow([views[0]]),

_expandedVideoRow([views[1]])

],

));

case3:

returnContainer(

child: Column(

children: [

_expandedVideoRow(views.sublist(0, 2)),

_expandedVideoRow(views.sublist(2, 3))

],

));

case4:

returnContainer(

child: Column(

children: [

_expandedVideoRow(views.sublist(0, 2)),

_expandedVideoRow(views.sublist(2, 4))

],

));

default:

}

returnContainer();

}

工具欄(掛斷、靜音、切換攝像頭)

在實現(xiàn)完視頻流布局后,我們接下來實現(xiàn)視頻通話的操作工具欄。工具欄里有三個按鈕,分別對應(yīng)靜音、掛斷、切換攝像頭的順序。用簡單的flexRow布局即可。

/// Toolbar layout

Widget _toolbar() {

returnContainer(

alignment: Alignment.bottomCenter,

padding: EdgeInsets.symmetric(vertical: 48),

child: Row(

mainAxisAlignment: MainAxisAlignment.center,

children: [

RawMaterialButton(

onPressed: () => _onToggleMute(),

child: newIcon(

muted ? Icons.mic : Icons.mic_off,

color: muted ? Colors.white : Colors.blueAccent,

size: 20.0,

),

shape: newCircleBorder(),

elevation: 2.0,

fillColor: muted?Colors.blueAccent : Colors.white,

padding: constEdgeInsets.all(12.0),

),

RawMaterialButton(

onPressed: () => _onCallEnd(context),

child: newIcon(

Icons.call_end,

color: Colors.white,

size: 35.0,

),

shape: newCircleBorder(),

elevation: 2.0,

fillColor: Colors.redAccent,

padding: constEdgeInsets.all(15.0),

),

RawMaterialButton(

onPressed: () => _onSwitchCamera(),

child: newIcon(

Icons.switch_camera,

color: Colors.blueAccent,

size: 20.0,

),

shape: newCircleBorder(),

elevation: 2.0,

fillColor: Colors.white,

padding: constEdgeInsets.all(12.0),

)

],

),

);

}

void _onCallEnd(BuildContext context) {

Navigator.pop(context);

}

void _onToggleMute() {

setState(() {

muted = !muted;

});

AgoraRtcEngine.muteLocalAudioStream(muted);

}

void _onSwitchCamera() {

AgoraRtcEngine.switchCamera();

}

最終整合

現(xiàn)在兩個部分的 UI 都完成了,我們接下去要將這兩個組件通過Stack組裝起來。

@override

Widget build(BuildContext context) {

returnScaffold(

appBar: AppBar(

title: Text(widget.channelName),

),

backgroundColor: Colors.black,

body: Center(

child: Stack(

children: [_viewRows(), _toolbar()],

)));

清理

若只在當(dāng)前頁面使用聲網(wǎng) SDK,則需要在離開前調(diào)用destroy接口將 SDK 實例銷毀。若需要跨頁面使用,則推薦將 SDK 實例做成單例以供不同頁面訪問。同時也要注意對原生渲染容器的釋放,可以至直接使用removeNativeView方法釋放對應(yīng)的原生容器。

@override

void dispose() {

// clean up native views & destroy sdk

_sessions.forEach((session) {

AgoraRtcEngine.removeNativeView(session.viewId);

});

_sessions.clear();

AgoraRtcEngine.destroy();

super.dispose();

}

最終效果:

總結(jié)

Flutter 作為新生事物,難免還是有他不成熟的地方,但我們已經(jīng)從他現(xiàn)在的進(jìn)步上看到了巨大的潛力。從目前的體驗來看,只要有充足的社區(qū)資源,在 Flutter 上開發(fā)跨平臺應(yīng)用還是比較舒服的。聲網(wǎng)提供的 Flutter SDK 基本已經(jīng)覆蓋了原生 SDK 提供的大部分方法,開發(fā)體驗基本可以和原生 SDK 開發(fā)保持一致。這次也是基于學(xué)習(xí)的態(tài)度寫下了這篇文章,希望對于想要使用 Flutter 開發(fā) RTC 應(yīng)用的同學(xué)有所幫助。

文中講解的完整代碼及 Agora Flutter SDK 可在 Github 獲取。

Agora Flutter SDK:

https://github.com/AgoraIO/Flutter-SDK

Quickstart Demo:

https://github.com/AgoraIO-Community/Agora-Flutter-Quickstart

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 視頻通話
    +關(guān)注

    關(guān)注

    0

    文章

    49

    瀏覽量

    12007

原文標(biāo)題:構(gòu)建你的第一個Flutter視頻通話應(yīng)用

文章出處:【微信號:shengwang-agora,微信公眾號:聲網(wǎng)Agora】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦
    熱點推薦

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售移動視頻
    發(fā)表于 03-19 15:53

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售移動視頻
    發(fā)表于 03-23 09:41

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售移動視頻
    發(fā)表于 03-31 09:30

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼出售移動視頻
    發(fā)表于 05-16 20:59

    移動視頻語音通話/全平臺視頻監(jiān)控/視頻直播源碼轉(zhuǎn)讓

    與android進(jìn)行視頻通話手機目前是320 以及640的分辨率,如果有需求可擴到更大。pc核心部分使用c++編寫, 服務(wù)端使用c#系統(tǒng)功能十分穩(wěn)定,和開源的sip 例如freeswitch等 穩(wěn)定不是
    發(fā)表于 05-18 13:09

    開發(fā)者福音——小e智能硬件開發(fā)平臺視頻體驗教程

    小e開發(fā)平臺視頻教http://v.youku.com/v_show/id_XMTQzNjE4NTk4MA==.html?qq-pf-to=pcqq.c2c
    發(fā)表于 01-05 19:13

    平臺的Qt調(diào)試作簡單的介紹

    Qt自帶調(diào)試功能,如果是主機上的程序可以直接調(diào)試,非常方便。同時,Qt是平臺的編程語言,在主機上調(diào)試其他平臺上的程序時就需要相應(yīng)的設(shè)置
    發(fā)表于 12-14 08:46

    視頻教程】紫光同創(chuàng)PDS軟件開發(fā)平臺視頻教程之IP使用及仿真

    視頻教程】紫光同創(chuàng)PDS軟件開發(fā)平臺視頻教程之IP使用及仿真~
    發(fā)表于 06-13 15:06

    i.MX51平臺視頻硬件解碼的研究與應(yīng)用

    i.MX51平臺視頻硬件解碼的研究與應(yīng)用
    發(fā)表于 09-23 16:59 ?29次下載

    中國聯(lián)通正式開啟邊緣業(yè)務(wù)平臺視頻轉(zhuǎn)碼及智能分析系統(tǒng)

    中國聯(lián)通日前發(fā)布公告,正式委托中國信息通信研究院(中國泰爾實驗室)開啟邊緣業(yè)務(wù)平臺視頻轉(zhuǎn)碼及智能分析系統(tǒng)測試,測試內(nèi)容及結(jié)果將作為后續(xù)"2018-2019年中國聯(lián)通邊緣業(yè)務(wù)平臺視頻轉(zhuǎn)碼及智能分析系統(tǒng)
    發(fā)表于 12-20 10:40 ?1594次閱讀

    全功能平臺的音樂制作平臺設(shè)計方案

    本文由小聲團(tuán)隊出品,小聲團(tuán)隊是專注于音頻&音樂技術(shù)的初創(chuàng)團(tuán)隊,深度使用 Flutter 構(gòu)建平臺應(yīng)用,希望與大家
    的頭像 發(fā)表于 03-11 09:41 ?1455次閱讀

    平臺構(gòu)建工具xmake簡介與安裝使用說明

    大家好,我是ZhengN。本次給大家分享優(yōu)秀的平臺構(gòu)建工具。
    的頭像 發(fā)表于 07-19 15:52 ?6893次閱讀

    平臺編譯工具CMake語法詳解

    cmake 是平臺、開源的構(gòu)建系統(tǒng)。它是
    的頭像 發(fā)表于 03-08 15:54 ?1847次閱讀

    基于WebAssembly構(gòu)建Web端音視頻通話引擎

    視頻通話引擎。今天將從背景、WebAssembly引擎、方案落地和問題及展望四方面展開介紹。 -01- 背景 隨著網(wǎng)絡(luò)基礎(chǔ)設(shè)施的
    的頭像 發(fā)表于 06-26 15:56 ?1155次閱讀
    基于WebAssembly<b class='flag-5'>構(gòu)建</b>Web端音<b class='flag-5'>視頻</b><b class='flag-5'>通話</b>引擎

    如何構(gòu)建高效、安全、可擴展的工廠IoT平臺

    的重要途徑。本文旨在探討如何構(gòu)建高效、安全、可擴展的工廠IoT平臺,以實現(xiàn)多工廠間的無縫集成與智能化管理。
    的頭像 發(fā)表于 10-25 17:21 ?441次閱讀