网易云信课题实践

网易云信&杭电课题实践,制作一个在线自习室app

在创新实践课上,老师给了我们一个网易云信与杭电合作的课题实践项目,我们选择了利用网易云信sdk制作一个在线自习APP。第一次做这种使用第三方sdk的项目,也是我的第一次制作安卓app。我负责的是客户端的制作,分享一下我的制作心得。
配套服务端传送门
https://blog.csdn.net/colorglass/article/details/117908845
不得不提网易云信的SDK是非常好用的,比如用户关系托管,加好友等,还有视频通话功能,头像显示功能,特别是UI库,能为我们省下很多时间,样式也比较简洁好看。不过在使用UI库的时候遇到了一些问题,一开始导入一直报错,本来想放弃,但是它真的非常方便,因为时间紧迫,最后还是向它妥协,最后在导师的帮助下解决了问题,因为导入的包有问题,在UI库的帮助下,消息界面的实现会更加方便快捷。其中他的消息收发界面是非常方便的,只需要自己配置头像点击事件即可,其中在配置标题点击事件的时候遇到了点问题,因为使用的是我的模块的页面导致了双向依赖,后来在指导下使用隐式跳转activity解决了这个问题在这里插入图片描述因为在线自习室没有用到群聊功能,session helper代码如下

public class SessionHelper {
    private String id;

    public static void init(String id) {
        // 设置会话中点击事件响应处理
        setSessionListener();
//        registerViewHolders();
        // 注册消息转发过滤器
        registerMsgForwardFilter();
        // 注册消息撤回过滤器
        registerMsgRevokeFilter(id);
//         注册消息撤回监听器
        registerMsgRevokeObserver();


    }

    private static void registerMsgForwardFilter() {
        NimUIKit.setMsgForwardFilter(new MsgForwardFilter() {

            @Override
            public boolean shouldIgnore(IMMessage message) {
                if (message.getMsgType() == MsgTypeEnum.robot && message.getAttachment() != null &&
                        ((RobotAttachment) message.getAttachment()).isRobotSend()) {
                    return true; // 如果是机器人发送的消息 不支持转发
                }
                return false;
            }
        });
    }
    /**
     * 消息撤回过滤器
     */
    private static void registerMsgRevokeFilter(String id) {
        NimUIKit.setMsgRevokeFilter(new MsgRevokeFilter() {

            @Override
            public boolean shouldIgnore(IMMessage message) {
                    if (id.equals(message.getSessionId())) {
                    // 发给我的电脑 不允许撤回
                    return true;
                }
                return false;
            }
        });
    }

    private static void setSessionListener() {
        SessionEventListener listener = new SessionEventListener() {
            @Override
            public void onAvatarClicked(Context context, IMMessage message) {
                // 一般用于打开用户资料页面
                PersonalInfo.start(context, message.getFromAccount());
            }

            @Override
            public void onAvatarLongClicked(Context context, IMMessage message) {
                // 一般用于群组@功能,或者弹出菜单,做拉黑,加好友等功能
            }
            @Override
            public void onAckMsgClicked(Context context, IMMessage message) {

            }
        };
        NimUIKit.setSessionListener(listener);
    }
    private static void registerMsgRevokeObserver() {
        NIMClient.getService(MsgServiceObserve.class).observeRevokeMessage(new NimMessageRevokeObserver(), true);
    }



}

然后是联系人列表,他有个自带的字母排序,他的名字优先级是备注>昵称>账号,同时也需要配置点击事件,它默认是打开聊天,但一般需要展示个人名片

public class ContactHelper {
    public static void init() {
        setContactEventListener();
    }

    private static void setContactEventListener() {
        NimUIKit.setContactEventListener(new ContactEventListener() {
            @Override
            public void onItemClick(Context context, String account) {
                PersonalInfo.start(context,account);
            }
            @Override
            public void onItemLongClick(Context context, String account) {

            }

            @Override
            public void onAvatarClick(Context context, String account) {
            }
        });
    }

}

这是UI库的消息记录
在这里插入图片描述搜索我参照demo写了两种搜索,一个是消息记录,右上角的图标有个红点提示功能,即有人加你好友会显示红点,有一个需要注意的是一定要在外面注册观察者,否则第一次进入会没有反应,一个是关键字搜索好友,其中搜索消息的锚点配置让我思考了很久,也就是点击消息要能 跳转到这条历史纪录的地方,
在这里插入图片描述

这是搜索的效果图,关于显示消息部分代码如下。`protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    View rootView = LayoutInflater.from(this).inflate(R.layout.message_history_activity, null);
    setContentView(rootView);

    ToolBarOptions options = new NimToolBarOptions();
    setToolBar(R.id.toolbar, options);

    onParseIntent();

    Container container = new Container(this, account, sessionType, this);
    messageListPanel = new MessageListPanelEx(container, rootView,anchor, true, false);
}
protected void onParseIntent() {
    anchor = (IMMessage) getIntent().getSerializableExtra(EXTRA_ANCHOR);
    account = anchor.getSessionId();
    sessionType = anchor.getSessionType();
    setTitle(UserInfoHelper.getUserTitleName(account, sessionType));
}`

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210526132724271.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2VyZWZhcw==,size_16,color_FFFFFF,t_70在这里插入图片描述在这里插入图片描述这是添加好友界面,参照demo写了重复信息的过滤器。同时同意或者拒绝好友对方会收到toast信息

个人信息界面如下,上传头像有一点让人头疼,做了好几天才让人满意,首先是点击的侧边栏,然后是再点击侧边栏头像可以更换。下面是有关上传头像以及更新给云信服务器代码。侧边栏使用的是drawerlayout非常好用
在这里插入图片描述

public void showChoosePicDialog(View v) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
        builder.setTitle("设置头像");
        String[] items = { "选择本地照片", "拍照" };
        builder.setNegativeButton("取消", null);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent;
                switch (which) {
                    case CHOOSE_PICTURE: // 选择本地照片
                        intent = new Intent(Intent.ACTION_GET_CONTENT);
                        intent.setType("image/*");
                        startActivityForResult(intent, CHOOSE_PICTURE);//启动系统图库
                        break;
                    case TAKE_PICTURE: // 拍照
                        intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        startActivityForResult(intent,TAKE_PICTURE);//启动摄像头
                        break;
                }
            }
        });
        builder.create().show();
    }

    private Uri saveBitmap(Bitmap bm) {
        tmpDir = new File(Environment.getExternalStorageDirectory() + "/com.app.avater");
        if (!tmpDir.exists()) {
            tmpDir.mkdir();
        }
        picture = new File(tmpDir.getAbsolutePath() + "avater.png");
        try {
            FileOutputStream fos = new FileOutputStream(picture);
            bm.compress(Bitmap.CompressFormat.PNG, 85, fos);
            fos.flush();
            fos.close();
            return Uri.fromFile(picture);//返回uri
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    private Uri convertUri(Uri uri) {
        InputStream is = null;
        try {
            is = getActivity().getContentResolver().openInputStream(uri);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            is.close();
            return saveBitmap(bitmap);//将头像保存到sd卡
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }




    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode ==TAKE_PICTURE) {
            //摄像头
            if (data == null) {
                return;//用户点击取消则直接返回
            } else {
                Bundle extras = data.getExtras();
                if (extras != null) {
                    Bitmap bm = extras.getParcelable("data");
                    uri_picture = saveBitmap(bm);//将文件保存到sd卡(直接是file类型的bitmap)
                    startPhotoZoom(uri_picture);

                }
            }
        } else if (requestCode == CHOOSE_PICTURE) {//图库
            if (data == null) {
                return;
            }
            uri_picture = data.getData();
            Uri fileUri = convertUri(uri_picture);
            startPhotoZoom(fileUri);

        } else if (requestCode == CROP_SMALL_PICTURE) {//得到图片裁剪后的数据
            if (data == null) {//用户点击取消则直接返回
                return;
            }
            Bundle extras = data.getExtras();
            if (extras == null) {
                return;
            }
            Bitmap bm = extras.getParcelable("data");

            icon = new File(tmpDir.getAbsolutePath() + "avater.png");    //获取最终icon
            try {
                FileOutputStream fos = new FileOutputStream(icon);
                bm.compress(Bitmap.CompressFormat.PNG, 85, fos);
                fos.flush();
                fos.close();} catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            updateAvatar(icon.getPath());
        }

    }
    /**
     * 裁剪图片方法实现
     *
     * @param uri
     */
    protected void startPhotoZoom(Uri uri) {
        // 调用系统中自带的图片剪裁
        finaluri=uri;
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", 300);
        intent.putExtra("outputY", 300);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, CROP_SMALL_PICTURE);
    }
     uploadAvatarFuture = NIMClient.getService(NosService.class).upload(file, PickImageAction.MIME_JPEG);
        uploadAvatarFuture.setCallback(new RequestCallbackWrapper<String>() {
            @Override
            public void onResult(int code, String url, Throwable exception) {
                if (code == ResponseCode.RES_SUCCESS && !TextUtils.isEmpty(url)) {
                    UserUpdateHelper.update(UserInfoFieldEnum.AVATAR, url, new RequestCallbackWrapper<Void>() {
                        @Override
                        public void onResult(int code, Void result, Throwable exception) {
                            if (code == ResponseCode.RES_SUCCESS) {
                                ToastHelper.showToast(getContext(), R.string.head_update_success);
                                onUpdateDone();
                            } else {
                                ToastHelper.showToast(getContext(), R.string.head_update_failed);
                            }
                        }
                    }); // 更新资料
                } else {
                    ToastHelper.showToast(getContext(), R.string.user_info_update_failed);
                    onUpdateDone();
                }
            }
        });

最重要的视频部分如下,
在这里插入图片描述

可以通过tag选择自习室标签,自习室有房主,这个列表搜索是用我们自己搭建的服务器数据库实现,点击即可进入自习室,进入前会先进行判断,你若是被踢过,除非房主邀请你,否在你不能再加入这个房间。下拉可以刷新列表,上拉可以加载更多,默认是6个记录,每次加载会多6条,但是每次搜索,换标签,刷新会让它重新变为六条。这三个按钮只有create创建房间有效,另外会在后期更改,是前期测试所用。点击创建会判断房间是否存在,若存在则会提示不进入房间。一般来说所有用户退出,房间会消失。自习室房主有转让权限,踢人的功能,所有人都可以点击他人的小视频与主视频切换,点击头像可查看他人信息,加好友,聊天等功能,房主退出权限会随机转让。邀请功能由信令实现,拒绝,接受,取消等对方都会收到信息,离线邀请会失败,如果他人在房间中也不可以邀请。在这里插入图片描述

 itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String account = friends.get(getLayoutPosition());
                    HttpPostfindisinroom httpPostfindisinroom=new HttpPostfindisinroom(account);
                    try {
                        httpPostfindisinroom.start();
                        if(httpPostfindisinroom.getRoomname().equals("")){
                            if(ishost){//是房主可以重新赋予权限
                                HttpPostunKick httpPostunKick=new HttpPostunKick(String.valueOf(roomid),account,myname);
                                httpPostunKick.start();
                                showdeldialog(account);
                            } //不是房主判断是否有权限
                            else {
                                HttpPostiskicked httpPostiskicked=new HttpPostiskicked(account,roomid);
                                httpPostiskicked.start();
                                if(httpPostiskicked.getData().equals("1")){
                                    ToastHelper.showToast(context, "ta已被踢过,请让房主邀请");
                                }
                                else {
                                    showdeldialog(account);
                                }
                            }
                        }
                        else {
                            ToastHelper.showToast(context, "对方在房间中");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
```private void showdeldialog(String friendid) {
        String requestId = String.valueOf(System.currentTimeMillis());
        CallParamBuilder paramBuilder = new CallParamBuilder(ChannelType.VIDEO, friendid, requestId);
        paramBuilder.channelExt(roomname);
        NIMClient.getService(SignallingService.class).call(paramBuilder).setCallback(
                new RequestCallbackWrapper<ChannelFullInfo>() {
                    @Override
                    public void onResult(int code, ChannelFullInfo channelFullInfo, Throwable throwable) {
                        //参考官方文档中关于api以及错误码的说明
                        if (code == ResponseCode.RES_SUCCESS) {
                            mychannelFullInfo=channelFullInfo;
                            ToastHelper.showToast(context, "邀请成功,等待对方接听");
                            AlertDialog alertDialog=new AlertDialog.Builder(context).setTitle("正在呼叫"+friendid).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    InviteParamBuilder param = new InviteParamBuilder(mychannelFullInfo.getChannelId(), friendid, requestId);
                                    NIMClient.getService(SignallingService.class).cancelInvite(param).setCallback(new RequestCallback<Void>() {
                                        @Override
                                        public void onSuccess(Void param) {
                                            Toast.makeText(context, "取消邀请成功 ", Toast.LENGTH_SHORT).show();
                                            NIMClient.getService(SignallingService.class).leave(mychannelFullInfo.getChannelId(), false, null);
                                        }
                                        @Override
                                        public void onFailed(int code) {
                                            Toast.makeText(context, "取消邀请失败 :code = " + code, Toast.LENGTH_SHORT).show();
                                        }

                                        @Override
                                        public void onException(Throwable exception) {
                                            Toast.makeText(context, "取消邀请异常 :exception = " + exception, Toast.LENGTH_SHORT).show();
                                        }
                                    });
                                }
                            }).create();
                            alertDialog.show();
                            alertDialog.setCanceledOnTouchOutside(false);
                            Observer onlineObserver = new Observer<ChannelCommonEvent>() {
                                @Override
                                public void onEvent(ChannelCommonEvent event) {
                                    SignallingEventType eventType = event.getEventType();
                                    switch (eventType) {
                                        case REJECT:
                                            ToastHelper.showToast(context, "对方拒绝了邀请");
                                            alertDialog.dismiss();
                                            break;
                                        case ACCEPT:
                                            ToastHelper.showToast(context, "对方接受了邀请");
                                            alertDialog.dismiss();
                                            break;
                                        default:break;
                                    }
                                    NIMClient.getService(SignallingService.class).leave(channelFullInfo.getChannelId(), false, null);
                                }
                            };
                            NIMClient.getService(SignallingServiceObserver.class).observeOnlineNotification(onlineObserver, true);
                        } else {
                            if(code==10202){
                                ToastHelper.showToast(context, "对方离线");
                            }
                        }
                    }
                });
``

 private void initinvitelisetener() {
        // 在线通知事件观察者
        Observer onlineObserver = new Observer<ChannelCommonEvent>() {
            @Override
            public void onEvent(ChannelCommonEvent event) {
                SignallingEventType eventType = event.getEventType();
                switch (eventType) {
                    case INVITE:
                        InvitedEvent invitedEvent = (InvitedEvent) event;
                        InviteParamBuilder inviteParam = new InviteParamBuilder(event.getChannelBaseInfo().getChannelId(),
                                event.getFromAccountId(),
                                invitedEvent.getRequestId());
                        LayoutInflater layoutInflater=LayoutInflater.from(MainActivity.this);
                        mydialog=layoutInflater.inflate(R.layout.friend_item,null);
                        intoroomname=event.getChannelBaseInfo().getChannelExt();
                        alertDialog=new AlertDialog.Builder(BaseActivity.getCurrentActivity()).setTitle("邀请你加入"+intoroomname).setView(mydialog).setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                HttpPostGetroominfo httpPostGetroominfo=new HttpPostGetroominfo(intoroomname,"0","");
                                try {
                                    httpPostGetroominfo.start();
                                    if(httpPostGetroominfo.getCode()==1){    //没房间
                                        Toast.makeText(BaseActivity.getCurrentActivity(),"房间不存在",Toast.LENGTH_SHORT).show();
                                        NIMClient.getService(SignallingService.class).rejectInvite(inviteParam);
                                    }
                                    else if(httpPostGetroominfo.getNum()==4){
                                        Toast.makeText(BaseActivity.getCurrentActivity(),"人满啦",Toast.LENGTH_SHORT).show();
                                        NIMClient.getService(SignallingService.class).rejectInvite(inviteParam);
                                    }
                                    else {           //进入房间,不是房主,
                                        NIMClient.getService(SignallingService.class).acceptInvite(inviteParam);
                                        roomid=httpPostGetroominfo.getRoomId();
                                        intoroom(myid,roomid,intoroomname);
//                                        intoroom(myid,roomname,false,httpPostGetroominfo.getRoomId());
                                    }
                                } catch (InterruptedException e) {
                                    e.printStackTrace();}
                            }
                        }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                NIMClient.getService(SignallingService.class).rejectInvite(inviteParam);
                            }
                        }).create();
                        if(alertDialog.isShowing()==false){
                            HeadImageView headImageView=mydialog.findViewById(R.id.invitehead);
                            String friendid=event.getFromAccountId();
                            headImageView.loadBuddyAvatar(friendid);
                            TextView textView=mydialog.findViewById(R.id.name);
                            if(NIMClient.getService(FriendService.class).isMyFriend(friendid)){
                                Friend friend = NIMClient.getService(FriendService.class).getFriendByAccount(friendid);
                                textView.setText(friend.getAlias());
                            }
                            else {
                                textView.setText(friendid);
                            }
                            alertDialog.setCanceledOnTouchOutside(false);
                            invitedEvent.getChannelBaseInfo().getChannelExt();
                            alertDialog.show();
                        }
                        break;
                    case CANCEL_INVITE:
                        if(alertDialog.isShowing()){
                            CanceledInviteEvent canceledInviteEvent = (CanceledInviteEvent) event;
                            alertDialog.dismiss();
                                if(NIMClient.getService(FriendService.class).isMyFriend(canceledInviteEvent.getToAccount())){
                                    Friend friend = NIMClient.getService(FriendService.class).getFriendByAccount(canceledInviteEvent.getToAccount());
                                    Toast.makeText(MainActivity.this,friend.getAlias()+"取消了邀请", Toast.LENGTH_LONG).show();
                                }
                                else {
                                    Toast.makeText(MainActivity.this,canceledInviteEvent.getFromAccountId()+"取消了邀请", Toast.LENGTH_LONG).show();
                                }

                        }
                        break;
                    default:break;
                }
            }
        };
//注册
        NIMClient.getService(SignallingServiceObserver.class).observeOnlineNotification(onlineObserver, true);

为了让对话框实现全局显示,在BaseActivity中配置了public static Activity getCurrentActivity() {
return mCurrentActivity;
},使其能在所有页面上显示。
在这里插入图片描述在这里插入图片描述无法呼叫离线好友,可主动取消呼叫,在对方应答后(回调)会话框会关闭

itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String account = friends.get(getLayoutPosition());
                    HttpPostfindisinroom httpPostfindisinroom=new HttpPostfindisinroom(account);
                    try {
                        httpPostfindisinroom.start();
                        if(httpPostfindisinroom.getRoomname().equals("")){
                            if(ishost){//是房主可以重新赋予权限
                                HttpPostunKick httpPostunKick=new HttpPostunKick(String.valueOf(roomid),account,myname);
                                httpPostunKick.start();
                                showdeldialog(account);
                            } //不是房主判断是否有权限
                            else {
                                HttpPostiskicked httpPostiskicked=new HttpPostiskicked(account,roomid);
                                httpPostiskicked.start();
                                if(httpPostiskicked.getData().equals("1")){
                                    ToastHelper.showToast(context, "ta已被踢过,请让房主邀请");
                                }
                                else {
                                    showdeldialog(account);
                                }
                            }
                        }
                        else {
                            ToastHelper.showToast(context, "对方在房间中");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }); private void showdeldialog(String friendid) {
        String requestId = String.valueOf(System.currentTimeMillis());
        CallParamBuilder paramBuilder = new CallParamBuilder(ChannelType.VIDEO, friendid, requestId);
        paramBuilder.channelExt(roomname);
        NIMClient.getService(SignallingService.class).call(paramBuilder).setCallback(
                new RequestCallbackWrapper<ChannelFullInfo>() {
                    @Override
                    public void onResult(int code, ChannelFullInfo channelFullInfo, Throwable throwable) {
                        //参考官方文档中关于api以及错误码的说明
                        if (code == ResponseCode.RES_SUCCESS) {
                            mychannelFullInfo=channelFullInfo;
                            ToastHelper.showToast(context, "邀请成功,等待对方接听");
                            AlertDialog alertDialog=new AlertDialog.Builder(context).setTitle("正在呼叫"+friendid).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    InviteParamBuilder param = new InviteParamBuilder(mychannelFullInfo.getChannelId(), friendid, requestId);
                                    NIMClient.getService(SignallingService.class).cancelInvite(param).setCallback(new RequestCallback<Void>() {
                                        @Override
                                        public void onSuccess(Void param) {
                                            Toast.makeText(context, "取消邀请成功 ", Toast.LENGTH_SHORT).show();
                                            NIMClient.getService(SignallingService.class).leave(mychannelFullInfo.getChannelId(), false, null);
                                        }
                                        @Override
                                        public void onFailed(int code) {
                                            Toast.makeText(context, "取消邀请失败 :code = " + code, Toast.LENGTH_SHORT).show();
                                        }

                                        @Override
                                        public void onException(Throwable exception) {
                                            Toast.makeText(context, "取消邀请异常 :exception = " + exception, Toast.LENGTH_SHORT).show();
                                        }
                                    });
                                }
                            }).create();
                            alertDialog.show();
                            alertDialog.setCanceledOnTouchOutside(false);
                            Observer onlineObserver = new Observer<ChannelCommonEvent>() {
                                @Override
                                public void onEvent(ChannelCommonEvent event) {
                                    SignallingEventType eventType = event.getEventType();
                                    switch (eventType) {
                                        case REJECT:
                                            ToastHelper.showToast(context, "对方拒绝了邀请");
                                            alertDialog.dismiss();
                                            break;
                                        case ACCEPT:
                                            ToastHelper.showToast(context, "对方接受了邀请");
                                            alertDialog.dismiss();
                                            break;
                                        default:break;
                                    }
                                    NIMClient.getService(SignallingService.class).leave(channelFullInfo.getChannelId(), false, null);
                                }
                            };
                            NIMClient.getService(SignallingServiceObserver.class).observeOnlineNotification(onlineObserver, true);
                        } else {
                            if(code==10202){
                                ToastHelper.showToast(context, "对方离线");
                            }
                        }
                    }
                });

在这里插入图片描述
在这里插入图片描述
视频如下,如果你是房主则会有三个按钮,不是则只有一个,头像点击可以进入个人页面,第二个是踢人,第三个是转让房主权限。默认进入房间是大的视频框,点击小的可以与大的切换视频对象。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值