视频弹幕的Python实现

视频弹幕管理器的实现方法

        直播接口提供的弹幕是通过消息的形式发送过来的, 在我们播放器进行展示的时候, 需要实现弹幕的移动与排布。实现的过程中将弹幕进行抽象,最终抽象出了的三个对象,其层次关系如图:

图1 弹幕管理抽象出的对象层次结构

文本对象: 负责文本的显示、隐藏与移动, 提供是否进入弹道和是否离开弹道的接口, 提供文本移动的TICK接口。

弹道对象:包含多个文本对象, 并对这些文本对象进行管理, 提供添加文本对象、删除文本对象,获得当前显示的文本对象个数, 当前是否可添加文本对象,清空弹道, 更新所有文本对象等接口。

弹幕对象:包含多个弹道对象,并对弹道对象进行管理, 提供依据屏幕大小自动分配弹道个数,隐藏和显示弹道, 弹幕消息队列缓存, 依据消息多少动态调整显示弹道个数, 以及消息选择弹道显示等接口。

三个层次的对象, 每个对象在对应层负责相应的功能, 各司其职, 就能完成整个弹幕显示的工作。

class TextSwimmer(object):
    """
        弹幕携带对象, 弹幕泳道的游泳者
    """

    def __init__(self, text_widget, msg, start_pos, end_pos):
        """
            游泳者的初始化
        """
        self.text = msg
        self.start_pos = start_pos
        self.end_pos = end_pos
        self.extern_dis = 30
        self.text_content = self.construct_show_text(msg)
        self.text_widget = text_widget.clone()
        self.text_widget.setAnchorPoint(cc.Vec2(0.0, 0.5))
        utils.set_content_rich(self.text_widget, self.text_content)
        shadow_color = utils.color_code_2_rgb('000000', 255)
        self.text_widget.enableShadow(shadow_color, cc.Size(3, -3), False)
        self.text_widget.setVisible(False)
        self.text_size = self.text_widget.getContentSize()
        self.last_move_time = time.time()
        self.interval = 50
        self.is_arrive = False
        self.start_swim()

    def construct_show_text(self, msg):
        """
            构建需要显示的信息
        """
        nick = '[' + msg.get('nick') + ']'
        msg_body = msg.get('msg_body')
        text_str = '#cFDFA02%s#n' % nick
        text_str += '#cFFFFFF%s#n' %  msg_body
        return text_str

    def start_swim(self):
        """
            弹幕文本移动, 开始游泳
        """
        self.text_widget.setVisible(True)
        pos_x = self.start_pos.x + self.extern_dis
        self.text_widget.setPosition(cc.Vec2(pos_x, 0))
        self.last_move_time = time.time()

    def hide_msg(self):
        """
            隐藏弹幕
        """
        self.text_widget.setVisible(False)

    def tick(self, speed):
        """
            保持移动
        """
        # 计算移动距离
        now = time.time()
        delta_time = now - self.last_move_time
        dis = delta_time * speed
        self.last_move_time = now

        # 进行移动, 并判定是否结束
        cur_pos = self.text_widget.getPosition()
        cur_pos.x += dis
 
        if cur_pos.x + self.text_size.width / 2.0 + self.extern_dis < self.end_pos.x:
            self.end_swim()
        else:
            self.text_widget.setPosition(cur_pos)

        
    def end_swim(self):
        """
            游泳结束
        """
        self.text_widget.setVisible(False)
        self.is_arrive = True

    def is_offshore(self):
        """
            下水的游泳者是否已经离岸, 下一个是否可以下水
        """
        cur_pos = self.text_widget.getPosition()
        text_size = self.text_widget.getContentSize() 
        text_width = text_size.width * self.text_widget.getScaleX()
        return cur_pos.x + text_width + self.interval < self.start_pos.x


class SwimLine(object):
    """
        弹幕泳道
    """

    def __init__(self, screen_widget, text_widget, position):
        """
            弹幕泳道
        """
        self.screen_widget = screen_widget
        self.text_widget = text_widget
        
        self.screen_size = screen_widget.getContentSize()
        self.root_node = ccui.Text.create()
        self.root_node.setAnchorPoint(cc.Vec2(0, 0.5))
        self.screen_widget.addChild(self.root_node)
        self.root_node.setPosition(position)
        self.position = position
        self.start_pos, self.end_pos = self.get_line_pos()
        self.last_text_swimmer = None
        self.text_swimmer_list = []
        self.speed = self.screen_size.width / float(random.uniform(5, 10))
        
    def add_text_swimmer(self, msg):
        """
            添加游泳者
        """
        # 游泳对象创建
        text_swimmer = TextSwimmer(self.text_widget, msg, self.start_pos, self.end_pos)
        self.text_swimmer_list.append(text_swimmer)
        self.last_text_swimmer = text_swimmer

        # 游泳对象控件的添加
        self.root_node.addChild(text_swimmer.text_widget)

    def remove_text_swimmer(self, text_swimmer):
        """
            移除游泳者
        """
        self.root_node.removeChild(text_swimmer.text_widget)
        self.text_swimmer_list.remove(text_swimmer)
        if self.last_text_swimmer is text_swimmer:
            self.last_text_swimmer = None

    def get_line_pos(self):
        """
            获取应道的开始与结束位置
        """
        x = self.screen_size.width
        y = self.position.y
        return cc.Vec2(x, y), cc.Vec2(0, y)

    def get_cur_speed(self):
        """
            获取泳道的速度
        """
        return -self.speed
    
    def get_swimmer_num(self):
        """
            获取当前游泳在人的数量
        """
        return len(self.text_swimmer_list)

    def set_last_swimmer(self, text_swimmer):
        """
            设置上一个下水的游泳者
        """
        self.last_text_swimmer = text_swimmer

    def is_swim_line_available(self):
        """
            当前泳道是都可下水
        """
        if not self.last_text_swimmer:
            return True
        return self.last_text_swimmer.is_offshore()
    
    def hide_msg(self):
        """
            隐藏弹幕
        """
        self.text_swimmer_list = []
        self.last_text_swimmer = None
        self.root_node.removeAllChildren()

    def tick(self):
        """
            泳道的tick
        """
        speed = self.get_cur_speed()

        # 每个游泳者进行游动
        arrive_swimmer_list = []
        for text_swimmer in self.text_swimmer_list:
            text_swimmer.tick(speed)
            if text_swimmer.is_arrive:
                arrive_swimmer_list.append(text_swimmer)

        # 剔除上岸的游泳者
        for text_swimmer in arrive_swimmer_list:
            self.remove_text_swimmer(text_swimmer)


class BarrageManager(object):
    """
        弹幕管理器
    """

    def __init__(self, screen_widget, text_widget, max_line):
        """
            弹幕管理器初始化
            @ screen_widget: 视频屏幕绑定的image控件
            @ open_line: 常开的弹幕通道数
            @ max_line: 弹幕较多时最多开的通道数
        """
        # 弹幕管理器的状态
        self.enable = True

        # 初始化弹幕参数 
        self.screen_widget = screen_widget
        self.min_line = max(int(max_line * 0.3), 1)
        self.max_line = max_line
        self.open_line = self.min_line
        self.max_msg_count = 1000
        self.msg_cache = Queue.Queue(maxsize=self.max_msg_count)
        self.swim_line_list = []

        # 初始化弹幕通道
        self.screen_size = self.screen_widget.getContentSize()
        self.gap_interval = self.screen_size.height / float(max(max_line, 1))
        for i in xrange(max_line):
            position = cc.Vec2(0, self.screen_size.height-self.gap_interval* (i+0.5))
            position.y *= self.screen_widget.getScaleX()
            swim_line = SwimLine(self.screen_widget, text_widget, position)
            self.swim_line_list.append(swim_line)
    
    def show_msg(self, msg):
        """
            显示弹幕MSG
        """
        if not self.enable:
            return
        
        if self.msg_cache.full():
            return

        try: self.msg_cache.put(msg, block=False)
        except: pass
    
    def hide_msg(self):
        """
            隐藏弹幕MSG
        """
        self.msg_cache.queue.clear()
        for swim_line in self.swim_line_list:
            if not swim_line:
                continue
            swim_line.hide_msg()
    
    def get_available_lines(self):
        """
            获取当前可添加弹幕的弹道, 并进行排序
        """
        # 获得可下水的泳道
        available_lines = []
        for i in xrange(self.open_line):
            swim_line = self.swim_line_list[i]
            if swim_line.is_swim_line_available():
                available_lines.append([swim_line, swim_line.get_swimmer_num()])
        available_lines.sort(key= lambda x: x[1])
        return available_lines

    def put_message_in_line(self, available_lines):
        """
            将弹幕放入弹道进行展示
        """
        # 获得需要下水的消息
        msg_list = []
        for i in xrange(len(available_lines)):
            if self.msg_cache.empty():
                break
            try: msg_list.append(self.msg_cache.get(block=False))
            except: pass
       
        # 安排消息下水, 松散优先, 相同随机
        for msg in msg_list:
            candidate_line_list = []
            for swim_line in available_lines:
                if swim_line[1] == available_lines[0][1]:
                    candidate_line_list.append(swim_line)
            swim_line_tuple = random.choice(candidate_line_list)
            swim_line_tuple[0].add_text_swimmer(msg)
            available_lines.remove(swim_line_tuple)

    def tick(self):
        """
            弹幕通道的tick
        """
        # 各各泳道的tick
        for swim_line in self.swim_line_list:
            swim_line.tick()

        # 获取可添加弹幕的弹道
        self.open_line = min(self.max_line, max(self.min_line, (self.msg_cache.qsize()-1)/5 + 1))
        available_lines = self.get_available_lines()

        # 将弹幕放入弹道
        self.put_message_in_line(available_lines)



猜你喜欢

转载自blog.csdn.net/ybn6775/article/details/80547952