iT邦幫忙

2

從零開始的聊天室建構(Android Studio)

前言

勿忘IT苦人多,本文會使用JAVA在Android Studio上撰寫,Server使用JavaScript在VScode上撰寫
需要一些android studio的基礎,相關環境方面的問題就不贅述,讓我們開始吧
前置作業:
1.安裝Java
2.安裝Android Studio
3.安裝Node.js
4.安裝VScode

5.安裝Nox,買Android手機(X

1.建立專案

創建一個專案(new project),選擇empty Activity -> Next -> Finish,這樣就創建了一個空白的專案。
在左邊打開Gradle Scripts這個資料夾,選取 build.gradle (Module: app)
我們要在這邊引入我們想要使用的功能,在dependencies{}裡面新增兩行代碼

implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'

然後使用Java8的環境
在android{}裡面新增以下代碼

compileOptions {
        sourceCompatibility '1.8'
        targetCompatibility '1.8'
    }

這樣就修改完成了
https://ithelp.ithome.com.tw/upload/images/20200514/201271091dqeHaobyB.jpg
文件上方會有一串提示文字,點選" Sync Now ",也可以點選綠色小槌子rebuild

再來去app -> manifests -> AndroidManifest.xml 增加權限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

INTERNET就是網路,READ_EXTERNAL_STORAGE是我們要傳遞圖片使用的,去讀取檔案的權限

在"application"內部新增一串代碼

android:usesCleartextTraffic="true"

這是用來支持app可以接受未加密的請求
接下來就是使用者界面了

2.使用者界面

主畫面,輸入自己的名字進入聊天室

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:layout_marginEnd="20dp"
        android:layout_marginBottom="20dp"
        android:background="@drawable/edit_text_design"
        android:hint="輸入您的名字"
        android:padding="10dp"
        android:textSize="16sp"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/enterBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/editText"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp"
        android:background="@color/colorPrimaryDark"
        android:text="進入聊天"
        android:textColor="#ffffff" />

</RelativeLayout>

文字區域設計 放在drawable裡面

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#cccccc" />
    <stroke android:color="#808080"
        android:width="1dp" />
    <corners android:radius="5dp"/>

</shape>

進入MainActivity.java
這裡主要是進入房間這個按鈕我們希望可以把"名字"這個資訊傳入聊天室裡面
創建一個Empty Activity 命名為 ChatActivity

package com.example.test0513;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 10);
        }

        EditText editText = findViewById(R.id.editText);

        findViewById(R.id.enterBtn)
            .setOnClickListener(v -> {

                Intent intent = new Intent(this,ChatActivity.class);
                intent.putExtra("name",editText.getText().toString());
                startActivity(intent);
            });
    }
}

在創建完ChatActivity之後會出現一個activity_chat.xml
建構聊天畫面

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ChatActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/messageEdit"
        android:id="@+id/recyclerView"/>

    <EditText
        android:id="@+id/messageEdit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="16dp"
        android:layout_toStartOf="@id/sendBtn"
        android:background="@drawable/edit_text_design"
        android:hint="Message..."
        android:padding="8dp"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/sendBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="16dp"
        android:padding="10dp"
        android:text="Send"
        android:visibility="invisible"
        android:textColor="@color/colorPrimary" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_image_black_24dp"
        android:tint="@color/colorPrimary"
        android:padding="8dp"
        android:layout_alignParentBottom="true"
        android:layout_toEndOf="@id/messageEdit"
        android:layout_marginBottom="16dp"
        android:id="@+id/pickImgBtn"
        android:layout_alignParentEnd="true" />


</RelativeLayout>

接下來都是新增Layout,在Layout資料夾點右鍵 -> New -> Layout Resource File
新增四個Layout 分別叫

item_received_message.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/nameTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:text="Name"
        android:textStyle="bold" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/nameTxt"
        android:background="@color/colorPrimary"
        android:padding="8dp"
        android:textColor="#ffffff"
        android:layout_marginEnd="64dp"
        android:layout_marginBottom="4dp"
        android:textSize="16sp"
        android:id="@+id/receivedTxt"
        android:text="Hello my name is yancehn"/>

</RelativeLayout>

item_received_photo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

   <TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Name"
       android:id="@+id/nameTxt"
       android:layout_margin="4dp"
       android:textStyle="bold"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="64dp"
        android:layout_marginStart="4dp"
        android:layout_marginBottom="4dp"
        android:layout_below="@id/nameTxt"
        android:id="@+id/imageView"
        android:src="@drawable/ic_image_black_24dp"/>

</RelativeLayout>

item_send_message.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:layout_marginStart="64dp"
        android:padding="8dp"
        android:textSize="16sp"
        android:background="#cccccc"
        android:text="Hello"
        android:id="@+id/sentTxt"/>

</RelativeLayout>

item_sent_image.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:layout_marginStart="64dp"
        android:padding="8dp"
        android:id="@+id/imageView"
        android:src="@drawable/ic_image_black_24dp"/>

</RelativeLayout>

這樣基本的使用者介面就完成了

3.WebSocket客戶端連線

在完成使用者介面之後,接下來我們要來跟伺服器建立連線,我們會使用okhttp3,他是一個網路請求的開源專案
伺服器位址我們可以先用"ws://echo.websocket.org" 這個位址會回傳你給他的內容(你跟他說hello 他就跟你說hello),可以用來檢視自己發的內容是不是正確的。

打開ChatActivity.java

package com.example.test0513;

import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

public class ChatActivity extends AppCompatActivity{

    private String name;
    private String SERVER_PATH = "ws://echo.websocket.org";
    private WebSocket webSocket;

    private EditText messageEdit;
    private View sendBtn, pickImgBtn;
    private RecyclerView recyclerView;
    
    private MessageAdapter messageAdapter;//先別管我

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        name = getIntent().getStringExtra("name");
        initiateSocketConnection();
    }

    private void initiateSocketConnection() {

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(SERVER_PATH).build();
        webSocket = client.newWebSocket(request, new SocketListener());
    }

    private class SocketListener extends WebSocketListener{
        
    }
}

點在WebSocketListener上按Ctrl+O ,override兩個方法,onOpen是當連接成功的時候會調用的方法
onMessage(websocket, string)是當收到string內容的時候會執行的方法,點選這兩個方法按下OK

private class SocketListener extends WebSocketListener{

        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            super.onOpen(webSocket, response);
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);
        }
    }

現在你的SoketListener會長這樣,然後我們希望在連接成功的時候我們可以獲得一條訊息
所以在onOpen裡面

runOnUiThread(() -> {
                Toast.makeText(ChatActivity.this, "Socket Connection Successful",
                        Toast.LENGTH_SHORT).show();
                initializeView();
            });

順帶一提,這裡的寫法沒有Java8是不支援的,然後如果連接成功我們希望初始化界面,新增一個initializeView方法
在那上面按下 Alt+Enter + Enter新增方法(在ChatActivity新增)

4.輸入內容

在initializeView這個方法裡面我們先把該找的Id找好讓他們找到歸宿
然後我們希望我們的輸入介面是這樣的,如果使用者沒輸入內容的時候我們希望按鈕變成發送圖片,如果使用者輸入文字按鈕就變成發送按鈕所以在這邊新增一個addTextChangedListener,他會在每次使用者打字或是刪減時被觸發

private void initializeView() {
        messageEdit = findViewById(R.id.messageEdit);
        sendBtn = findViewById(R.id.sendBtn);
        pickImgBtn = findViewById(R.id.pickImgBtn);
        recyclerView = findViewById(R.id.recyclerView);

        messageEdit.addTextChangedListener(this);
        messageAdapter = new MessageAdapter(getLayoutInflater());//先別管我
        recyclerView.setAdapter(messageAdapter);//先別管我
        recyclerView.setLayoutManager(new LinearLayoutManager(this));//先別管我
    }

在this上面按Alt+Enter,選擇讓ChatActivity實作TextWatcher,按下OK會實作三個方法,在afterTextChanged這裡去判斷是要顯示哪種Button(afterTextChanged是三個的其中一個)

@Override
    public void afterTextChanged(Editable s) {
        String string = s.toString().trim();
        if(string.isEmpty()){
            resetMessageEdit();
        }else{
            sendBtn.setVisibility(View.VISIBLE);
            pickImgBtn.setVisibility(View.INVISIBLE);
        }
    }

    private void resetMessageEdit() {
        messageEdit.removeTextChangedListener(this);

        messageEdit.setText("");
        sendBtn.setVisibility(View.INVISIBLE);
        pickImgBtn.setVisibility(View.VISIBLE);

        messageEdit.addTextChangedListener(this);
    }

5.發送按鈕

再來我們要賦予按鈕功能,回到initializeView這個方法

private void initializeView() {
        ...略

        sendBtn.setOnClickListener(v -> {
            JSONObject jsonObject = new JSONObject();
            try {
                jsonObject.put("name", name);
                jsonObject.put("message", messageEdit.getText().toString());

                webSocket.send(jsonObject.toString());
                jsonObject.put("isSent", true);
                messageAdapter.addItem(jsonObject);//先別管我
                resetMessageEdit();

            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
}

在這裡,如果使用者按了發送扭,我們就把他的名字還有他輸入的內容發送到伺服器,並把輸入的內容清空
接下來就是獲取圖片的按鈕

private void initializeView() {
        ...略

        pickImgBtn.setOnClickListener(v -> {
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.setType("image/*");

            startActivityForResult(Intent.createChooser(intent, "Pick image"),
                    IMAGE_REQUEST_ID);
        });
    }
    
@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == IMAGE_REQUEST_ID && resultCode == RESULT_OK) {
            try {
                InputStream is = getContentResolver().openInputStream(data.getData());
                Bitmap image = BitmapFactory.decodeStream(is);
                sendImage(image);
            } catch (FileNotFoundException | JSONException e) {
                e.printStackTrace();
            }
        }
    }

    private void sendImage(Bitmap image) throws JSONException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);

        String base64String = android.util.Base64.encodeToString(outputStream.toByteArray(),
                Base64.DEFAULT);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", name);
        jsonObject.put("image", base64String);
        webSocket.send(jsonObject.toString());

        jsonObject.put("isSent", true);
        messageAdapter.addItem(jsonObject);//先別管我
    }

這裡的IMAGE_REQUEST_ID可以設置隨意的數字,當使用者點選圖片按扭的時候,會跳轉到檔案選擇畫面,
這段代碼會把使用者選取的點陣圖片壓縮並轉成Base64編碼發送,這裡我們是用android的Base64類別

6.接收訊息

回到SocketListener的onMessage這裡我們談過,是當接收到消息的時候會執行的方法

@Override
        public void onMessage(okhttp3.WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);

            runOnUiThread(() -> {
                try {
                    JSONObject jsonObject = new JSONObject(text);
                    jsonObject.put("isSent", false);
                    messageAdapter.addItem(jsonObject);//先別管我
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            });
        }

在這裡我們把收到的字串內容標記 "isSent" 為false,用來區別這個內容不是自己發出的

7.聊天內容顯示

新增一個新的Java class,命名為MessageAdapter,並繼承RecyclerView.Adapter,按下Alt+Enter
實作所有方法,我們先前創作了四個layout分別是自己發送的文字內容,自己發送的圖片,與別人發的文字和圖片
根據不同的情況在使用者介面要放置不同的layout

package com.example.test0513;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

public class MessageAdapter extends RecyclerView.Adapter {

    private final int TYPE_MESSAGE_SENT = 0;
    private final int TYPE_MESSAGE_RECEIVED = 1;
    private final int TYPE_IMAGE_SENT = 2;
    private final int TYPE_IMAGE_RECEIVED = 3;

    private LayoutInflater inflater;
    private List<JSONObject> messages = new ArrayList<>();

    public MessageAdapter(LayoutInflater inflater){
        this.inflater = inflater;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }
}

所以我們需要用到LayoutInflater,創建一個List來放置所有的聊天內容,
接下來我們要創建四個ViewHolder去控制四個layout的內容

private class SentMessageHolder extends RecyclerView.ViewHolder {

        TextView messageTxt;

        public SentMessageHolder(@NonNull View itemView) {
            super(itemView);

            messageTxt = itemView.findViewById(R.id.sentTxt);
        }
    }

    private class SentImageHolder extends RecyclerView.ViewHolder{

        ImageView imageView;

        public SentImageHolder(@NonNull View itemView) {
            super(itemView);

            imageView = itemView.findViewById(R.id.imageView);
        }
    }

    private class ReceivedMessageHolder extends RecyclerView.ViewHolder{

        TextView nameTxt, messageTxt;

        public ReceivedMessageHolder(@NonNull View itemView) {
            super(itemView);

            nameTxt = itemView.findViewById(R.id.nameTxt);
            messageTxt = itemView.findViewById(R.id.receivedTxt);
        }
    }

    private class ReceivedImageHolder extends RecyclerView.ViewHolder{

        ImageView imageView;
        TextView nameTxt;

        public ReceivedImageHolder(@NonNull View itemView) {
            super(itemView);

            imageView = itemView.findViewById(R.id.imageView);
            nameTxt = itemView.findViewById(R.id.nameTxt);
        }
    }

然後創建一個getItemViewType方法去判斷是哪一種情況要用哪一個Layout
如果isSent是true就是自己發送的,如果裡面有message就是文字內容

@Override
    public int getItemViewType(int position) {

        JSONObject message = messages.get(position);

        try {
            if(message.getBoolean("isSent")){
                if(message.has("message")){
                    return TYPE_MESSAGE_SENT;
                }else{
                    return TYPE_IMAGE_SENT;
                }
            }else{
                if(message.has("message")){
                    return TYPE_MESSAGE_RECEIVED;
                }else{
                    return TYPE_IMAGE_RECEIVED;
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return -1;
    }

再來修改getItemCount這個方法的內容為

@Override
    public int getItemCount() {
        return messages.size();
    }

來獲取有幾條訊息

之後讓我們看到onCreateViewHolder這個方法,我們要在這裡套入要用的Layout
並把這些Layout內容放到我們先前做的ViewHolder裡面

@NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View view;

        switch(viewType){
            case TYPE_MESSAGE_SENT:
                view = inflater.inflate(R.layout.item_send_message, parent, false);
                return new SentMessageHolder(view);
            case TYPE_MESSAGE_RECEIVED:
                view = inflater.inflate(R.layout.item_received_message, parent, false);
                return new ReceivedMessageHolder(view);
            case TYPE_IMAGE_SENT:
                view = inflater.inflate(R.layout.item_sent_image, parent, false);
                return new SentImageHolder(view);
            case TYPE_IMAGE_RECEIVED:
                view = inflater.inflate(R.layout.item_received_photo, parent, false);
                return new ReceivedImageHolder(view);
        }

        return null;
    }

最後就是在onBindViewHolder放入我們要這些Layout顯示的內容

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        JSONObject message = messages.get(position);

        try {
            if(message.getBoolean("isSent")){
                if(message.has("message")){
                    SentMessageHolder messageHolder = (SentMessageHolder) holder;
                    messageHolder.messageTxt.setText(message.getString("message"));
                }else{
                    SentImageHolder imageHolder = (SentImageHolder) holder;
                    Bitmap bitmap = getBitmapFromString(message.getString("image"));

                    imageHolder.imageView.setImageBitmap(bitmap);
                }
            }else{
                if(message.has("message")){
                    ReceivedMessageHolder messageHolder = (ReceivedMessageHolder) holder;
                    messageHolder.nameTxt.setText(message.getString("name"));
                    messageHolder.messageTxt.setText(message.getString("message"));
                }else{
                    ReceivedImageHolder imageHolder = (ReceivedImageHolder) holder;
                    imageHolder.nameTxt.setText(message.getString("name"));

                    Bitmap bitmap = getBitmapFromString(message.getString("image"));
                    imageHolder.imageView.setImageBitmap(bitmap);
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private Bitmap getBitmapFromString(String image) {
        byte[] bytes = Base64.decode(image, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }

我們再新增一個方法,在有新的聊天內容的時候調用這個方法,把內容放到我們的messages裡面
並告知Adapter內部的內容改變了

public void addItem(JSONObject jsonObject){
        messages.add(jsonObject);
        notifyDataSetChanged();
    }

這樣客戶端的部分就完成了,開起來做測試
https://ithelp.ithome.com.tw/upload/images/20200515/20127109vn7cZxcdQ8.png

現在是輸入甚麼文字就會收到甚麼文字,圖片也是一樣

Server

在搜尋列執行cmd -> npm install websocket
並打上ipconfig/all去查看自己的ip 把SERVER_PATH的內容換成
ws://自己的IP:3000

再把下面的代碼放在VScode上運行下
就可以多人在同一個聊天室進行聊天了

const SocketServer = require('websocket').server;
const http = require('http');
const port = 3000;

const server = http.createServer((req, res) => {});

server.listen(port, () => {
    console.log(`Listening on port ${port}...`);
});

wsServer = new SocketServer({httpServer:server});

const connections = [];

wsServer.on('request', (req) => {
    const connection = req.accept();
    console.log('new connection');
    connections.push(connection);

    connection.on('message', (mes) => {
        connections.forEach(element => {
            if(element != connection)
                element.sendUTF(mes.utf8Data);
        });
    });

    connection.on('close', (resCode, des) => {
        console.log('connection closed');
        connections.splice(connections.indexOf(connection), 1);
    });
});

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
s951080603
iT邦新手 5 級 ‧ 2022-11-24 22:56:08

您好,程式的部分完全照著您操作,
卻發生了點擊發送按鈕無法傳送,
另外 UI 畫面也和您的不太一樣,
我的部分 Send 跟 Img 兩個是分上下的,
方便請教您問題會是什麼嗎?

我要留言

立即登入留言