精华内容
下载资源
问答
  • 弹幕实现逻辑
    千次阅读
    2017-06-10 02:52:58

    我之前也是看博客写的,比较简单,思路比较清晰,我用了一个懒人框架,不用做findbyid以及监听事件

    public class MainActivity extends Activity {
        @BindView(R.id.edit_text)
        EditText editText;
        @BindView(R.id.send)
        Button send;
        @BindView(R.id.operation_layout)
        LinearLayout operationLayout;
        /*DanmakuContext可以用于对弹幕的各种全局配置进行设定,如设置字体、设置最大显示行数等*/
        private DanmakuContext danmakuContext;
        private boolean showDanmaku;
        @BindView(R.id.video)
        VideoView video;/*视频播放器*/
        @BindView(R.id.activity_main)
        RelativeLayout activityMain;
        @BindView(R.id.danmaku_view)
        DanmakuView danmakuView;
        /*弹幕的解析器*/
        private BaseDanmakuParser parser = new BaseDanmakuParser() {
            @Override
            protected IDanmakus parse() {
                return new Danmakus();
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
            /*设置了一个视频文件的地址*/
            video.setVideoPath(Environment.getExternalStorageDirectory() + "/Pixels.mp4");
            /*开始播放*/
            video.start();
            /*只是设置一些提升绘制效率,又调用了setCallback()方法来设置回调函数。*/
            danmakuView.enableDanmakuDrawingCache(true);
            danmakuView.setCallback(new DrawHandler.Callback() {
                @Override
                public void prepared() {
                    showDanmaku = true;
                    danmakuView.start();
                    generateSomeDanmaku();
                }
    
                @Override
                public void updateTimer(DanmakuTimer timer) {
    
                }
    
                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
    
                }
    
                @Override
                public void drawingFinished() {
    
                }
            });
            /*没有什么好设置的,直接默认就可以了*/
            danmakuContext = DanmakuContext.create();
            /*prepare()方法来进行准备*/
            danmakuView.prepare(parser, danmakuContext);
            getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
                    new View.OnSystemUiVisibilityChangeListener() {
                @Override
                public void onSystemUiVisibilityChange(int visibility) {
                    if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
                        onWindowFocusChanged(true);
                    }
                }
            });
    
        }
    
        /*沉浸模式onWindowFocusChanged,就是为了让图片没有导航栏,如果是视频一般都是横向
        * 为了界面更好看*/
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            /*以下代码可以直接粘,这种模式5.0的系统才会有,
            所以要先做一个判断,然后在getWindow().getDecorView()方法获取到了当前界面的DecorView
            setSystemUiVisibility()设置他的可见性,下面的都是他的方法,详情百度
            http://blog.csdn.net/guolin_blog/article/details/51763825
    
             * */
            if (hasFocus && Build.VERSION.SDK_INT >= 19) {
                View decorView = getWindow().getDecorView();
                decorView.setSystemUiVisibility(
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_FULLSCREEN
                                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
            }
        }
        /*第3步在配置清单中,设置他的横屏,配置sd卡的权限*/
        /*第4步是弹幕的实现效果,这样可以用一个DanmakuFlameMaster
           先添加一个依赖app/build.gradle文件,并在dependencies闭包中添加如下依赖:
            compile 'com.github.ctiao:DanmakuFlameMaster:0.5.3'
            第5步可以在布局中引用了,有一点要记住DanmakuView写在VideoView的下面,
            因为RelativeLayout中后添加的控件会被覆盖在上面。
    
        * */
    
        /**DanmakuView已经在正常工作了,但是屏幕上没有任何弹幕信息的话我们也看不出效果,
         * 因此我们还要增加一个添加弹幕消息的功能。
         * 观察addDanmaku()方法,这个方法就是用于向DanmakuView中添加一条弹幕消息的
         * 向弹幕View中添加一条弹幕
         *
         * @param content    弹幕的具体内容
         * @param withBorder 弹幕是否有边框
         */
        private void addDanmaku(String content, boolean withBorder) {
            /*BaseDanmaku创建一个实例,TYPE_SCROLL_RL表示这是一条从右向左滚动的弹幕*/
            BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
           /*设置弹幕的颜色字体显示时间等等*/
            danmaku.text = content;
            danmaku.padding = 5;
            danmaku.textSize = sp2px(20);
            /*文字设成白色,*/
            danmaku.textColor = Color.WHITE;
            danmaku.setTime(danmakuView.getCurrentTime());
            /*是否带框*/
            if (withBorder) {
                danmaku.borderColor = Color.GREEN;
            }
            danmakuView.addDanmaku(danmaku);
        }
    
        /**
         * 随机生成一些弹幕内容以供测试
         */
        private void generateSomeDanmaku() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (showDanmaku) {
                        int time = new Random().nextInt(300);
                        String content = "" + time + time;
                        addDanmaku(content, false);
                        try {
                            Thread.sleep(time);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    
        /**
         * sp转px的方法。
         */
        public int sp2px(float spValue) {
            final float fontScale = getResources().getDisplayMetrics().scaledDensity;
            return (int) (spValue * fontScale + 0.5f);
        }
    
    
    
        @OnClick(R.id.send)
        public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.send:
                    String content = editText.getText().toString();
                    if (!TextUtils.isEmpty(content)) {
                        addDanmaku(content, true);
                        editText.setText("");
                    }
                    break;
            }
        }
    
        /*DanmakuView设置了一个点击事件,当点击屏幕时就会触发这个点击事件。
        然后进行判断,如果操作界面是隐藏的就将它显示出来,
        如果操作界面是显示的就将它隐藏掉,这样就可以简单地通过点击屏幕来实现操作界面的隐藏和显示了。*/
        @OnClick(R.id.danmaku_view)
        public void onViewClicked() {
            if (operationLayout.getVisibility() == View.GONE) {
                operationLayout.setVisibility(View.VISIBLE);
            } else {
                operationLayout.setVisibility(View.GONE);
            }
            /*接下来我们又给发送按钮注册了一个点击事件,当点击发送时,
            获取EditText中的输入内容,然后调用addDanmaku()方法将这条消息添加到DanmakuView上。
            另外,这条弹幕是由我们自己发送的,因此addDanmaku()方法的第二个参数要传入true。
    最后,由于系统输入法弹出的时候会导致焦点丢失,从而退出沉浸式模式,
    因此这里还对系统全局的UI变化进行了监听,保证程序一直可以处于沉浸式模式。*/
            getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
                    new View.OnSystemUiVisibilityChangeListener() {
                        @Override
                        public void onSystemUiVisibilityChange(int visibility) {
                            if (visibility == View.SYSTEM_UI_FLAG_VISIBLE) {
                                onWindowFocusChanged(true);
                            }
                        }
                    });
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (danmakuView != null && danmakuView.isPrepared() && danmakuView.isPaused()) {
                danmakuView.resume();
            }
        }
    
    
        @Override
        protected void onPause() {
            super.onPause();
            if (danmakuView != null && danmakuView.isPrepared()) {
                danmakuView.pause();
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            showDanmaku = false;
            if (danmakuView != null) {
                danmakuView.release();
                danmakuView = null;
            }
        }
    
    
    }
    
    ------------------------------------------------------------------------------
     
    
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/activity_main"
        android:background="#000">
        <!--Videoview 本地视频播放器-->
        <VideoView
            android:id="@+id/video"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"/>
        <!--弹幕布局-->
        <master.flame.danmaku.ui.widget.DanmakuView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/danmaku_view"/>
        <LinearLayout
            android:id="@+id/operation_layout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:background="#fff"
            android:visibility="gone">
    
            <EditText
                android:id="@+id/edit_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                />
    
            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="Send" />
        </LinearLayout>
    
    
    </RelativeLayout>
    
    ----------------------------------------------------------------------------------
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <activity android:name=".MainActivity" android:screenOrientation="landscape"
        android:configChanges="orientation|keyboardHidden|screenLayout|screenSize">



     
    
    
    
    更多相关内容
  • Android弹幕实现:基于B站弹幕开源系统(3) 本文在附录1,2的基础上再次对异步获取弹幕并显示弹幕完善逻辑和代码,集中在上层Java代码部分: package zhangphil.danmaku; import android.app.Activity; im...
    

    Android弹幕实现:基于B站弹幕开源系统(3)

    本文在附录1,2的基础上再次对异步获取弹幕并显示弹幕完善逻辑和代码,集中在上层Java代码部分:

    package zhangphil.danmaku;
    
    import android.app.Activity;
    import android.graphics.Color;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.text.TextUtils;
    import android.view.View;
    import android.widget.Button;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.ScheduledThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    import master.flame.danmaku.danmaku.model.BaseDanmaku;
    import master.flame.danmaku.danmaku.model.DanmakuTimer;
    import master.flame.danmaku.danmaku.model.IDisplayer;
    import master.flame.danmaku.danmaku.model.android.DanmakuContext;
    import master.flame.danmaku.ui.widget.DanmakuView;
    
    public class MainActivity extends Activity {
    
        private DanmakuView mDanmakuView;
        private DanmakuContext mContext;
        private AcFunDanmakuParser mParser;
    
        private final int MAX_DANMAKU_LINES = 8; //弹幕在屏幕显示的最大行数
    
        private ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor = null;
        private ConcurrentLinkedQueue<DanmakuMsg> mQueue = null; //所有的弹幕数据存取队列,在这里做线程的弹幕取和存
        private ArrayList<DanmakuMsg> danmakuLists = null;//每次请求最新的弹幕数据后缓存list
    
        private final int WHAT_GET_LIST_DATA = 0xffa01;
        private final int WHAT_DISPLAY_SINGLE_DANMAKU = 0xffa02;
    
        private final int BASE_TIME = 400;
        private final int BASE_TIME_ADD = 100;
    
        //标志文本弹幕的序列号
        //区别不同弹幕
        private static int danmakuTextMsgId = 0;
    
        private final int[] colors = {Color.RED, Color.YELLOW, Color.BLUE, Color.GREEN, Color.CYAN, Color.DKGRAY};
    
        private Handler mDanmakuHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
    
                switch (msg.what) {
                    case WHAT_GET_LIST_DATA:
                        mDanmakuHandler.removeMessages(WHAT_GET_LIST_DATA);
    
                        if (danmakuLists != null && !danmakuLists.isEmpty()) {
                            mQueue.addAll(danmakuLists);
                            danmakuLists.clear();
    
                            if (!mQueue.isEmpty())
                                mDanmakuHandler.sendEmptyMessage(WHAT_DISPLAY_SINGLE_DANMAKU);
                        }
    
                        break;
    
                    case WHAT_DISPLAY_SINGLE_DANMAKU:
                        mDanmakuHandler.removeMessages(WHAT_DISPLAY_SINGLE_DANMAKU);
                        displayDanmaku();
                        break;
                }
            }
        };
    
        /**
         * 弹幕数据封装的类(bean)
         */
        private class DanmakuMsg {
            public String msg;
        }
    
        private void displayDanmaku() {
            boolean p = mDanmakuView.isPaused();
            //如果当前的弹幕由于Android生命周期的原因进入暂停状态,那么不应该不停的消耗弹幕数据
            //要知道,在这里发出一个handler消息,那么将会消费(删掉)ConcurrentLinkedQueue头部的数据
            if (!mQueue.isEmpty() && !p) {
                DanmakuMsg dm = mQueue.poll();
                if (!TextUtils.isEmpty(dm.msg)) {
                    addDanmaku(dm.msg, true);
                }
    
                mDanmakuHandler.sendEmptyMessageDelayed(WHAT_DISPLAY_SINGLE_DANMAKU, (long) (Math.random() * BASE_TIME) + BASE_TIME_ADD);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            danmakuLists = new ArrayList<>();
            mQueue = new ConcurrentLinkedQueue<>();
            mDanmakuView = (DanmakuView) findViewById(R.id.danmakuView);
    
            initDanmaku();
    
            mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
            GetDanmakuMessageTask mTask = new GetDanmakuMessageTask();
            //延迟0秒执行,每隔若干秒周期执行一次任务
            mScheduledThreadPoolExecutor.scheduleAtFixedRate(mTask, 0, 5, TimeUnit.SECONDS);
    
            Button show = (Button) findViewById(R.id.show);
            Button hide = (Button) findViewById(R.id.hide);
            Button sendText = (Button) findViewById(R.id.sendText);
            Button pause = (Button) findViewById(R.id.pause);
            Button resume = (Button) findViewById(R.id.resume);
            Button clear = (Button) findViewById(R.id.clear);
    
            show.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mDanmakuView.show();
                }
            });
    
            hide.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mDanmakuView.hide();
                }
            });
    
            sendText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //每点击一次按钮发送一条弹幕
                    sendTextMessage();
                }
            });
    
            pause.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mDanmakuView.pause();
                }
            });
    
            resume.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mDanmakuView.resume();
                }
            });
    
            clear.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    clearDanmaku();
                }
            });
        }
    
        /**
         * 假设该线程任务模拟的就是从网络中取弹幕数据的耗时操作
         * 假设这些弹幕数据序列是有序的。
         */
        private class GetDanmakuMessageTask implements Runnable {
            @Override
            public void run() {
                danmakuLists.clear();
    
                int count = (int) (Math.random() * 50);
                for (int i = 0; i < count; i++) {
                    DanmakuMsg message = new DanmakuMsg();
                    message.msg = "弹幕:" + danmakuTextMsgId;
                    danmakuLists.add(message);
    
                    danmakuTextMsgId++;
                }
    
                if (!danmakuLists.isEmpty()) {
                    Message msg = mDanmakuHandler.obtainMessage();
                    msg.what = WHAT_GET_LIST_DATA;
                    mDanmakuHandler.sendMessage(msg);
                }
            }
        }
    
        /**
         * 驱动弹幕显示机制重新运作起来
         */
        private void resumeDanmaku() {
            if (!mQueue.isEmpty())
                mDanmakuHandler.sendEmptyMessageDelayed(WHAT_DISPLAY_SINGLE_DANMAKU, (int) (Math.random() * BASE_TIME) + BASE_TIME_ADD);
        }
    
        private void clearDanmaku() {
            if (danmakuLists != null && !danmakuLists.isEmpty()) {
                danmakuLists.clear();
            }
    
            if (mQueue != null && !mQueue.isEmpty())
                mQueue.clear();
    
            mDanmakuView.clearDanmakusOnScreen();
            mDanmakuView.clear();
        }
    
        private void initDanmaku() {
            mContext = DanmakuContext.create();
    
            // 设置最大显示行数
            HashMap<Integer, Integer> maxLinesPair = new HashMap<>();
            maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, MAX_DANMAKU_LINES); // 滚动弹幕最大显示5行
    
            // 设置是否禁止重叠
            HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<>();
            overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
            overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);
    
            //普通文本弹幕也描边设置样式
            //如果是图文混合编排编排,最后不要描边
            mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 10) //描边的厚度
                    .setDuplicateMergingEnabled(false)
                    .setScrollSpeedFactor(1.2f) //弹幕的速度。注意!此值越小,速度越快!值越大,速度越慢。// by phil
                    .setScaleTextSize(1.2f)  //缩放的值
    //        .setCacheStuffer(new BackgroundCacheStuffer())  // 绘制背景使用BackgroundCacheStuffer
                    .setMaximumLines(maxLinesPair)
                    .preventOverlapping(overlappingEnablePair);
    
            mParser = new AcFunDanmakuParser();
            mDanmakuView.prepare(mParser, mContext);
    
            //mDanmakuView.showFPS(true);
            mDanmakuView.enableDanmakuDrawingCache(true);
    
            if (mDanmakuView != null) {
                mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
                    @Override
                    public void updateTimer(DanmakuTimer timer) {
                    }
    
                    @Override
                    public void drawingFinished() {
    
                    }
    
                    @Override
                    public void danmakuShown(BaseDanmaku danmaku) {
                        //Log.d("弹幕文本", "danmakuShown text=" + danmaku.text);
                    }
    
                    @Override
                    public void prepared() {
                        mDanmakuView.start();
                    }
                });
            }
        }
    
        private void sendTextMessage() {
            addDanmaku("zhangphil@csdn: " + System.currentTimeMillis(), true);
        }
    
        private void addDanmaku(CharSequence cs, boolean islive) {
            BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
            if (danmaku == null || mDanmakuView == null) {
                return;
            }
    
            danmaku.text = cs;
            danmaku.padding = 5;
            danmaku.priority = 0;  // 可能会被各种过滤器过滤并隐藏显示
            danmaku.isLive = islive;
            danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);
            danmaku.textSize = 20f * (mParser.getDisplayer().getDensity() - 0.6f); //文本弹幕字体大小
            danmaku.textColor = getRandomColor(); //文本的颜色
            danmaku.textShadowColor = getRandomColor(); //文本弹幕描边的颜色
            //danmaku.underlineColor = Color.DKGRAY; //文本弹幕下划线的颜色
            danmaku.borderColor = getRandomColor(); //边框的颜色
    
            mDanmakuView.addDanmaku(danmaku);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            if (mDanmakuView != null && mDanmakuView.isPrepared()) {
                mDanmakuView.pause();
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
                mDanmakuView.resume();
    
                //重新启动handler消息机制,触发弹幕显示
                //如果没有这一个方法,那么显示弹幕的机制将失灵(失去驱动)
                //这个方法就是重新激发弹幕显示的handler机制。
                resumeDanmaku();
            }
        }
    
        private void closeGetDanmakuMessage() {
            if (mScheduledThreadPoolExecutor != null)
                mScheduledThreadPoolExecutor.shutdown();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mDanmakuView != null) {
                // dont forget release!
                mDanmakuView.release();
                mDanmakuView = null;
            }
    
            closeGetDanmakuMessage();
        }
    
        /**
         * 从一系列颜色中随机选择一种颜色
         *
         * @return
         */
        private int getRandomColor() {
            int i = ((int) (Math.random() * 10)) % colors.length;
            return colors[i];
        }
    }


    代码运行结果如图:


    附录:
    1,《Android弹幕实现:基于B站弹幕开源系统(1)》链接:http://blog.csdn.net/zhangphil/article/details/68067100
    2,《Android弹幕实现:基于B站弹幕开源系统(2)》链接:http://blog.csdn.net/zhangphil/article/details/68114226
    3,《Java ConcurrentLinkedQueue队列线程安全操作》链接:http://blog.csdn.net/zhangphil/article/details/65936066

    转载于:https://my.oschina.net/zhangphil/blog/1602209

    展开全文
  • 本篇文章主要介绍了CSS3 实现弹幕的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 这篇文章主要介绍了关于实现弹幕效果的方法总结(css和canvas) ,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下之前在一个移动端的抽奖页面中,在抽奖结果的展示窗口需要弹幕轮播显示,之前踩过一些...

    这篇文章主要介绍了关于实现弹幕效果的方法总结(css和canvas) ,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

    之前在一个移动端的抽奖页面中,在抽奖结果的展示窗口需要弹幕轮播显示,之前踩过一些小坑,现在总结一下前端弹幕效果的实现方式。

    css3实现乞丐版的弹幕

    css3弹幕性能优化

    canvas实现弹幕

    canva弹幕的扩展功

    1. css3实现乞丐版的弹幕

    (1)如何通过css3实现弹幕

    首先来看如何通过css的方法实现一个最简单的弹幕:

    首先在html中定义一条弹幕的dom结构:

    我是弹幕

    弹幕的移动可以通过移动这个block来实现,以从右向左移动的弹幕为例,弹幕的初始位置在容器的最左侧且贴边隐藏(弹幕的最左边与容器的最右贴合),可以通过绝对定位加transform来实现:.block{

    position:absolute;

    }

    初始位置:from{

    left:100%;

    transform:translateX(0)

    }

    移动到最左边的结束位置为(弹幕的最右边与容器的最左边贴合):to{

    left:0;

    transform:translateX(-100%)

    }

    起始位置和结束位置的具体图示如下所示:

    389d15186758fbab6591f1a386f7e820.png

    根据起始位置和结束位置可以定义完整的两帧弹幕动画:@keyframes barrage{

    from{

    left:100%;

    transform:translateX(0);

    }

    to{

    left:0;

    transform:translateX(-100%);

    }

    }

    给弹幕元素引入这个动画:.block{

    position:absolute;

    /* other decorate style */

    animation:barrage 5s linear 0s;

    }

    这样就可以实现一个乞丐版的弹幕效果:

    aa1b21b199260afb736331ca6793ed13.gif

    (2)通过绝对定位和left实现弹幕的缺陷

    首先明确一下css的渲染过程I)根据HTML的结构生成DOM树(DOM树中包含了display:none的节点)

    II)在DOM树的基础上,根据节点的几何属性(margin/padding/width/height/left等)生成render树

    III)在render树的基础上继续渲染color,font等属性

    其中如果I)中和II)中的属性发生变化会发生reflow(回流),如果仅仅III)中的属性发生改变,只会发生repaint(重绘)。显然从css的渲染过程我们也可以看出来:reflow(回流)必伴随着重绘。

    reflow(回流):当render树中的一部分或者全部因为大小边距等问题发生改变而需要重建的过程叫做回流

    repaint(重绘):当元素的一部分属性发生变化,如外观背景色不会引起布局变化而需要重新渲染的过程叫做重绘

    reflow(回流)会影响浏览器css的渲染速度,因此在做网页性能优化的时候要减少回流的发生。

    在第一节,我们通过left属性,实现了弹幕的效果,left会改变元素的布局,因此会发生reflow(回流),表现在移动端页面上会造成弹幕动画的卡顿。

    2. css3弹幕性能优化

    我们直到了第一节中的弹幕动画存在卡顿的问题,下面我们看看如何解决动画的卡顿。

    (1)CSS开启硬件加速

    在浏览器中用css开启硬件加速,使用GPU(Graphics Processing Unit)可以提升网页性能。鉴于此,我们可以发挥GPU的力量,从而使我们的网站或应用表现的更为流畅。

    CSS animations, transforms 以及 transitions 不会自动开启GPU加速,而是由浏览器的缓慢的软件渲染引擎来执行。那我们怎样才可以切换到GPU模式呢,很多浏览器提供了某些触发的CSS规则。

    比较常见的方式是,我们可以通过3d变化(translate3d属性)来开启硬件加速,鉴于此,我们修改动画为:@keyframes barrage{

    from{

    left:100%;

    transform:translate3d(0,0,0);

    }

    to{

    left:0;

    transform:translate3d(-100%,0,0);

    }

    }

    这样就可以通过开启硬件加速的方式,优化网页性能。但是这种方式没有从根本上解决问题,同时使用GPU增加了内存的使用,会减少移动设备的电池寿命等等。

    (2)不改变left属性

    第二种方法,就是想办法在弹幕动画的前后不改变left属性的值,这样就不会发生reflow。

    我们想仅仅通过translateX来确定弹幕节点的初始位置,但是translateX(-100%)是相对于弹幕节点本身的,而不是相对于父元素,因此我们耦合js和css,在js中获取弹幕节点所在的父元素的宽度,接着根据宽度来定义弹幕节点的初始位置。

    以父元素为body时为例://css

    .block{

    position:absolute;

    left:0;

    visibility:hidden;

    /* other decorate style */

    animation:barrage 5s linear 0s;

    }

    //js

    let style = document.createElement('style');

    document.head.appendChild(style);

    let width = window.innerWidth;

    let from = `from { visibility: visible; -webkit-transform: translateX(${width}px); }`;

    let to = `to { visibility: visible; -webkit-transform: translateX(-100%); }`;

    style.sheet.insertRule(`@-webkit-keyframes barrage { ${from} ${to} }`, 0);

    除了耦合js计算了父元素的宽度,从而确定弹幕节点的初始位置之外,这里在弹幕节点中我们为了防止初始位置就有显示,增加了visibility:hidden属性。防止弹幕节点在未确定初始位置时就显示在父容器内。只有弹幕开始从初始位置滚动,才会变得可见。

    但是这种css的实现方式,在实现弹幕的扩展功能方面比较麻烦,比如如何控制弹幕暂停等等。

    3. canvas实现弹幕

    除了通过css实现弹幕的方法之外,通过canvas也可以实现弹幕。

    通过canvas实现弹幕的原理就是时时的重绘文字,下面来一步步的实现。获取画布

    let canvas = document.getElementById('canvas');

    let ctx = canvas.getContext('2d');

    绘制文字ctx.font = '20px Microsoft YaHei';

    ctx.fillStyle = '#000000';

    ctx.fillText('canvas 绘制文字', x, y);

    上面的fillText就是实现弹幕效果的主要api,其中x表示横方向的坐标,y表示纵方向的坐标,只要时时的改变x,y进行重绘,就可以实现动态的弹幕效果。清除绘制内容

    ctx.clearRect(0, 0, width, height);

    具体实现

    通过定时器,定时改变x,y,每次改变之前先进性清屏,然后根据改变后的x,y进行重绘。当存在多条弹幕的情况下,定义:let colorArr=_this.getColor(color); 弹幕数组多对应的颜色数组

    let numArrL=_this.getLeft(); 弹幕数组所对应的x坐标位置数组

    let numArrT=_this.getTop(); 弹幕数组所对应的y坐标位置数组

    let speedArr=_this.getSpeed(); 弹幕数组所对应的弹幕移动速度数组

    定时的重绘弹幕函数为:_this.timer=setInterval(function(){

    ctx.clearRect(0,0,canvas.width,canvas.height);

    ctx.save();

    for(let j=0;j

    numArrL[j]-=speedArr[j];

    ctx.fillStyle = colorArr[j]

    ctx.fillText(barrageList[j],numArrL[j],numArrT[j]);

    ctx.restore();

    },16.7);

    实现的效果为:

    42ac99b4ec0c9f4f9c4cbcef8e230f1d.gif

    4. canva弹幕的扩展功能

    通过canvas实现弹幕的方式,很方便做比如暂停弹幕滚动等扩展功能,此外,也可以给弹幕增加头像,给每条弹幕增加边框等等功能,以后再补充。

    相关推荐:

    展开全文
  • 主要介绍了Android仿斗鱼直播的弹幕效果的相关资料,需要的朋友可以参考下
  • Java 实现视频弹幕功能

    千次阅读 2021-11-17 01:01:15
    2021年了,还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,ppt都上弹幕玩法了,不整个弹幕都说不过去了,今天笔者就抽空做了一个实时视频弹幕交互功能的实现,不得不说这样的形式...

    2021年了,还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,ppt都上弹幕玩法了,不整个弹幕都说不过去了,今天笔者就抽空做了一个实时视频弹幕交互功能的实现,不得不说这样的形式为看视频看直播,讲义PPT,抽奖等形式增加了许多乐趣。

    1 技术选型

    1.1 netty

    官方对于netty的描述:

    https://netty.io/

    主要关键词描述:netty是异步事件驱动网络框架,可做各种协议服务端,并且支持了FTP,SMTP,HTTP等很多协议,并且性能,稳定性,灵活性都很棒。

    b43d9938d71908a2feed3edd853c5f9a.png

    可以看到netty整体架构上分了三个部分:

    • 以零拷贝,一致性接口,扩展事件模型的底层核心。

    • Socket,Datagram,Pipe,Http Tunnel作为传输媒介。

    • 传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,Google Protobuf等各种各种的传输形式。

    1.2 WebSocket

    WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

    1.3 为什么做这样的技术选型。

    • 由上述可知,实时直播交互作为互动式是一个双向数据传输过程。所以使用webSocket。

    • netty本身支持了webSocket协议的实现,让实现更加简单方便。

    2 实现思路

    2.1 服务架构

    整体架构是所有客户端都和我的服务端开启一个双向通道的架构。

    8489ca8e953ff270cae6ae641bc5b569.png

    2.2 传输流程

    173718e3a940701a97656c01425245b9.png

    3 实现效果

    3.1 视频展示

    先看看效果吧,是不是perfect,接下来就来看具体代码是怎么实现的吧。

    9cbdbb68249651e474d98600807c1d29.png
    视频直播弹幕示例

    4 代码实现

    4.1 项目结构

    一个maven项目,将代码放一个包下就行。

    80a6b10cdde2b965363b907448f0ee81.png

    4.2 Java服务端

    Java服务端代码,总共三个类,Server,Initailizer和 Handler。

    4.2.1 先做一个netty nio的服务端:

    一个nio的服务,开启一个tcp端口。

    /**
     * Copyright(c)lbhbinhao@163.com
     * @author liubinhao
     * @date 2021/1/14
     * ++++ ______                           ______             ______
     * +++/     /|                         /     /|           /     /|
     * +/_____/  |                       /_____/  |         /_____/  |
     * |     |   |                      |     |   |        |     |   |
     * |     |   |                      |     |   |________|     |   |
     * |     |   |                      |     |  /         |     |   |
     * |     |   |                      |     |/___________|     |   |
     * |     |   |___________________   |     |____________|     |   |
     * |     |  /                  / |  |     |   |        |     |   |
     * |     |/ _________________/  /   |     |  /         |     |  /
     * |_________________________|/b    |_____|/           |_____|/
     */
    public enum BulletChatServer {
        /**
         * Server instance
         */
        SERVER;
    
        private BulletChatServer(){
            EventLoopGroup mainGroup = new NioEventLoopGroup();
            EventLoopGroup subGroup  = new NioEventLoopGroup();
            ServerBootstrap server = new ServerBootstrap();
            server.group(mainGroup,subGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new BulletChatInitializer());
            ChannelFuture future = server.bind(9123);
        }
    
        public static void main(String[] args) {
    
        }
    
    }

    4.2.2 服务端的具体处理逻辑

    /**
     * Copyright(c)lbhbinhao@163.com
     *
     * @author liubinhao
     * @date 2021/1/14
     * ++++ ______                           ______             ______
     * +++/     /|                         /     /|           /     /|
     * +/_____/  |                       /_____/  |         /_____/  |
     * |     |   |                      |     |   |        |     |   |
     * |     |   |                      |     |   |________|     |   |
     * |     |   |                      |     |  /         |     |   |
     * |     |   |                      |     |/___________|     |   |
     * |     |   |___________________   |     |____________|     |   |
     * |     |  /                  / |  |     |   |        |     |   |
     * |     |/ _________________/  /   |     |  /         |     |  /
     * |_________________________|/b    |_____|/           |_____|/
     */
    
    public class BulletChatInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new HttpServerCodec());
            pipeline.addLast(new ChunkedWriteHandler());
            pipeline.addLast(new HttpObjectAggregator(1024*64));
            pipeline.addLast(new IdleStateHandler(8, 10, 12));
            pipeline.addLast(new WebSocketServerProtocolHandler("/lbh"));
            pipeline.addLast(new BulletChatHandler());
        }
    }

    后台处理逻辑,接受到消息,写出到所有的客户端:

    /**
     * Copyright(c)lbhbinhao@163.com
     *
     * @author liubinhao
     * @date 2021/1/14
     * ++++ ______                           ______             ______
     * +++/     /|                         /     /|           /     /|
     * +/_____/  |                       /_____/  |         /_____/  |
     * |     |   |                      |     |   |        |     |   |
     * |     |   |                      |     |   |________|     |   |
     * |     |   |                      |     |  /         |     |   |
     * |     |   |                      |     |/___________|     |   |
     * |     |   |___________________   |     |____________|     |   |
     * |     |  /                  / |  |     |   |        |     |   |
     * |     |/ _________________/  /   |     |  /         |     |  /
     * |_________________________|/b    |_____|/           |_____|/
     */
    
    public class BulletChatHandler  extends SimpleChannelInboundHandler<TextWebSocketFrame> {
        // 用于记录和管理所有客户端的channel
        public static ChannelGroup channels =
                new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
            // 获取客户端传输过来的消息
            String content = msg.text();
            System.err.println("收到消息:"+ content);
            channels.writeAndFlush(new TextWebSocketFrame(content));
            System.err.println("写出消息完成:"+content);
        }
    
        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            channels.add(ctx.channel());
        }
    
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
            String channelId = ctx.channel().id().asShortText();
            System.out.println("客户端被移除,channelId为:" + channelId);
            channels.remove(ctx.channel());
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            // 发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除
            ctx.channel().close();
            channels.remove(ctx.channel());
        }
    
    }

    4.3 网页客户端实现

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Netty视频弹幕实现 Author:Binhao Liu</title>
        <link rel="stylesheet" href="">
        <style type="text/css" media="screen">
            * {
                margin: 0px;
                padding: 0px
            }
    
            html, body {
                height: 100%
            }
    
            body {
                overflow: hidden;
                background-color: #FFF;
                text-align: center;
            }
    
            .flex-column {
                display: flex;
                flex-direction: column;
                justify-content: space-between;, align-items: center;
            }
    
            .flex-row {
                display: flex;
                flex-direction: row;
                justify-content: center;
                align-items: center;
            }
    
            .wrap {
                overflow: hidden;
                width: 70%;
                height: 600px;
                margin: 100px auto;
                padding: 20px;
                background-color: transparent;
                box-shadow: 0 0 9px #222;
                border-radius: 20px;
            }
    
            .wrap .box {
                position: relative;
                width: 100%;
                height: 90%;
                background-color: #000000;
                border-radius: 10px
            }
    
            .wrap .box span {
                position: absolute;
                top: 10px;
                left: 20px;
                display: block;
                padding: 10px;
                color: #336688
            }
    
            .wrap .send {
                display: flex;
                width: 100%;
                height: 10%;
                background-color: #000000;
                border-radius: 8px
            }
    
            .wrap .send input {
                width: 40%;
                height: 60%;
                border: 0;
                outline: 0;
                border-radius: 5px 0px 0px 5px;
                box-shadow: 0px 0px 5px #d9d9d9;
                text-indent: 1em
            }
    
            .wrap .send .send-btn {
                width: 100px;
                height: 60%;
                background-color: #fe943b;
                color: #FFF;
                text-align: center;
                border-radius: 0px 5px 5px 0px;
                line-height: 30px;
                cursor: pointer;
            }
    
            .wrap .send .send-btn:hover {
                background-color: #4cacdc
            }
        </style>
    </head>
    <script>
        var ws = new WebSocket("ws://localhost:9123/lbh");
    
        ws.onopen = function () {
            // Web Socket 已连接上,使用 send() 方法发送数据
            alert("数据发送中...");
        };
        ws.onmessage = function (e) {
            console.log("接受到消息:"+e.data);
            createEle(e.data);
        };
        ws.onclose = function () {
            // 关闭 websocket
            alert("连接已关闭...");
        };
        function sendMsg(msg) {
            ws.send(msg)
        }
    
    
    </script>
    <body>
    <div class="wrap flex-column">
        <div class="box">
            <video src="shape.mp4" width="100%" height="100%" controls autoplay></video>
        </div>
        <div class="send flex-row">
    
            <input type="text" class="con" placeholder="弹幕发送[]~(^v^)~*"/>
    
            <div class="send-btn" onclick="javascript:sendMsg(document.querySelector('.con').value)">发送</div>
        </div>
    </div>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" type="text/javascript"></script>
    <script>
        //1.获取元素
        var oBox = document.querySelector('.box');   //获取.box元素
        var cW = oBox.offsetWidth;   //获取box的宽度
        var cH = oBox.offsetHeight;   //获取box的高度
        function createEle(txt) {
            //动态生成span标签
            var oMessage = document.createElement('span');   //创建标签
            oMessage.innerHTML = txt;   //接收参数txt并且生成替换内容
            oMessage.style.left = cW + 'px';  //初始化生成位置x
            oBox.appendChild(oMessage);   //把标签塞到oBox里面
            roll.call(oMessage, {
                //call改变函数内部this的指向
                timing: ['linear', 'ease-out'][~~(Math.random() * 2)],
                color: '#' + (~~(Math.random() * (1 << 24))).toString(16),
                top: random(0, cH),
                fontSize: random(16, 32)
            });
        }
    
        function roll(opt) {
            //弹幕滚动
            //如果对象中不存在timing 初始化
            opt.timing = opt.timing || 'linear';
            opt.color = opt.color || '#fff';
            opt.top = opt.top || 0;
            opt.fontSize = opt.fontSize || 16;
            this._left = parseInt(this.offsetLeft);   //获取当前left的值
            this.style.color = opt.color;   //初始化颜色
            this.style.top = opt.top + 'px';
            this.style.fontSize = opt.fontSize + 'px';
            this.timer = setInterval(function () {
                if (this._left <= 100) {
                    clearInterval(this.timer);   //终止定时器
                    this.parentNode.removeChild(this);
                    return;   //终止函数
                }
                switch (opt.timing) {
                    case 'linear':   //如果匀速
                        this._left += -2;
                        break;
                    case 'ease-out':   //
                        this._left += (0 - this._left) * .01;
                        break;
                }
                this.style.left = this._left + 'px';
            }.bind(this), 1000 / 60);
        }
    
        function random(start, end) {
            //随机数封装
            return start + ~~(Math.random() * (end - start));
        }
    
        var aLi = document.querySelectorAll('li');   //10
    
        function forEach(ele, cb) {
            for (var i = 0, len = aLi.length; i < len; i++) {
                cb && cb(ele[i], i);
            }
        }
    
        forEach(aLi, function (ele, i) {
            ele.style.left = i * 100 + 'px';
        });
        //产生闭包
        var obj = {
            num: 1,
            add: function () {
                this.num++;   //obj.num = 2;
                (function () {
                    console.log(this.num);
                })
            }
        };
        obj.add();//window
    
    </script>
    </body>
    </html>

    这样一个实时的视频弹幕功能就完成啦,是不是很简单,各位小伙伴快来试试吧。

    5 小结

    上班撸代码,下班继续撸代码写博客,这个还是很简单,笔者写这个的时候一会儿就写完了,不过这也得益于笔者很久以前就写过netty的服务,对于Http,Tcp之类协议也比较熟悉,只有前端会有些难度,问下度娘,也很快能做完,在此分享出来与诸君分享,有问题可找笔者交流。

    往期推荐

    Redis分布式锁需要考虑的这些事!

    Redis 面试题!精华!收藏一波 !

    解决kafka 消息堆积问题的排查及调优

    MySQL 的隔离级别和事务需要知道的

    3行代码能写出8个接口!同事这样做的

    Java 服务性能优化,提升QPS

    API 接口文档平台,多人协作太顺手了

    0ecd6deb4c6f57cbca19b349f0714a1e.gif

    回复干货】获取精选干货视频教程

    回复加群】加入疑难问题攻坚交流群

    回复mat】获取内存溢出问题分析详细文档教程

    回复赚钱】获取用java写一个能赚钱的微信机器人

    回复副业】获取程序员副业攻略一份

    68412f855f66e7a862bf11f4cb9c115e.png

    0271eb628406bbf4a451bd05a8c41599.gif

    戳这儿

    展开全文
  • 实现弹幕效果 接下来我们开始实现弹幕效果。弹幕其实也就是一个自定义的 View,它的上面可以显示类似于跑马灯的文字效果。观众们发表的评论都会在弹幕上显示出来,但又会很快地移出屏幕,既可以起到互动的作用,...
  • ????????关注后回复“进群”,拉你进程序员交流群????????作者丨视频团队刘小壮来源丨搜狐技术产品(ID:sohu-tech)本文字数:6244字预计阅读时间:35分钟+介绍弹幕诞生于日本的视频平台...
  • 项目需要实现弹幕,网上参考了各种方法,最后觉得transform+transition实现的效果在移动设备上性能最好,在iphone6和红米4上测试,看不到卡顿的感觉。用jquery的animate动画在移动设备上有明显的卡顿。本文主要介绍...
  • android实现简单弹幕

    2019-03-26 15:15:03
    android端实现,简单的弹幕功能;可以自定义弹幕的样式和使用逻辑
  • 前端实现直播弹幕

    千次阅读 2019-07-21 17:49:13
    2,建立所需要的dom节点并添加到dom池里面,dom节点总数=轨道数X每条轨道可放置的最大弹幕数,并把建好的dom节点加入到可视区容器里面通过添加class使其定位到可视区外部,并绑定相应的事件进行逻辑处理(注意作用域...
  • 那就是弹幕,现在也越来越多的人喜欢上了弹幕,甚至有人常说,‘正经人谁看视频啊,我是来看弹幕的????’,下面看下它的效果: 相信小伙伴们都看过了,那么它实现的原理是什么呢,那么我们前端怎么用我们web技术...
  • 弹幕在直播中是一个比较重要的功能,而开源的B站弹幕可以让我们轻松的实现这个功能。 其实现原理大概就是在页面的上方盖了一层全透明的View,然后再View中添加需要的弹幕效果。 上图: 1、首先,在项目中添加依赖:...
  • 弹幕墙的实现

    千次阅读 2018-08-29 10:34:05
    1.首先要考虑弹幕墙需要什么:一面墙,输入框,发射按钮,清除弹幕按钮; 2.其次弹幕上墙以后需要移动,从墙的最右边移动到最左边,当移动到最左边时,这条弹幕就要消失; 3.初步的想法应该就是当在输入框输入你要...
  • android端实现,简单的弹幕功能;可以自定义弹幕的样式和使用逻辑
  • 对于视频网站来说弹幕是一个十分常见的功能, 目前业界比较出名的弹幕库是B站的DanmakuFlameMaster... 本文主要分析一下用户发送一条从右向左滚动的弹幕实现逻辑(不涉及视频弹幕时间同步等相关逻辑):下图是用户...
  • DanPlayer 弹幕播放器 请以最终实际使用效果为准。...函数逻辑自由实现,可以是提交弹幕内容到服务器 返回true,会渲染这次发送的弹幕到屏幕 返回false,则不渲染这次发送的弹幕到屏幕 HLS和MPD支持选择
  • Android弹幕功能实现,模仿斗鱼直播的弹幕效果

    万次阅读 多人点赞 2016-10-18 08:01:32
    记得之前有位朋友在我的公众号里问过我,像直播的那种弹幕功能该如何实现?如今直播行业确实是非常火爆啊,大大小小的公司都要涉足一下直播的领域,用斗鱼的话来讲,现在就是千播之战。而弹幕则无疑是直播功能当中...
  • 之前在公司有一个项目,需要使用uni-app实现一个弹幕的效果,展示已付款用户。 当时项目赶时间,使用了一个插件,后来琢磨了一下实现思路: 分析 一个弹幕功能,首先需要拿到弹幕内容,这个不用多说。 除此以外,...
  • 本篇文章转自唐子玄的博客,文章主要分享了他对弹幕效果实现的过程,相信会对大家有所帮助! 原文地址: https://juejin.cn/post/7004603099113340936 / 引子 / 实现如上图所示的弹幕,第一个想到的方案是“动画...
  • 本文将以微信小程序实时弹幕功能为例,向大家介绍高实时业务场景的功能技术实现过程。 实时弹幕功能的技术实现 实现方式对比 短轮询 短轮询是客户端每隔一段时间向服务器发出请求,服务器端在收到请求后,不论...
  • 主要介绍了使用Go基于WebSocket构建千万级视频直播弹幕系统,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • Android的弹幕功能实现

    万次阅读 2018-03-17 16:36:31
    首先我们来看一下斗鱼上的弹幕效果,如下图所示: 这是一个Dota2游戏直播的界面,我们可以看到,在游戏界面的上方有很多的弹幕,看直播的观众们就是在这里进行讨论的。 那么这样的一个界面该如何实现呢?其实并...
  • 基于 Swoole 实现支持高并发的实时弹幕功能(下)由 学院君 创建于1年前, 最后更新于 1年前版本号 #112184 views6 likes0 collects我们接着上篇教程来完成弹幕服务端以及客户端与服务端交互的开发,首先来实现服务端 ...
  • 以下是一些写脚ben时获得的数据记录,最后面是脚ben代码。 抓包拿到的弹幕数据接口:"https://api.bilibili.com/x/v1/dm/list.so?oid=" + cid ...弹幕的返回数据含义如下: <d p="1611.18000,1,2
  • 基于原生js实现主流弹幕的所有功能

    千次阅读 2019-04-24 10:34:29
    下面介绍的就是目前主流直播弹幕网站的弹幕实现方式,包括弹幕指定暂停,点赞,举报功能. 首先来看一张实现动图 原理简介 动画效果使用css3中的 transition 属性实现 只有transition是远远不够的,因为...
  • uniapp小程序实现弹幕功能

    千次阅读 2021-08-24 21:08:33
    实现弹幕功能主要用的是css的animation 属性 animation 属性是一个简写属性,用于设置六个动画属性: animation-name 规定需要绑定到选择器的 keyframe 名称。 animation-duration 规定完成动画所花费的时间,以秒...
  • Bilibili高并发实时弹幕系统的实现

    千次阅读 2018-06-07 17:00:06
    原文地址:https://blog.csdn.net/zhiboshequ/article/details/68489960点此打开原文站点高并发实时弹幕是一种互动的体验。对于互动来说,考虑最多的地方就是:高稳定性、高可用性以及低延迟这三个方面。高稳定性,...
  • 浅谈前端弹幕的设计

    2021-08-24 00:37:41
    无法复制加载中的内容 不管是通过 Canvas 还是 DOM 实现弹幕,需要的方法都是相似的:添加新弹幕到等待队列、寻找合适的轨道、从等待队列中抽取弹幕并放入轨道、整体渲染、清空。因此 BaseStage 可以通过编排抽象...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,275
精华内容 2,110
关键字:

弹幕实现逻辑

友情链接: PictureViewer.zip