简易视频播放器-全屏播放



一、课程说明
上一次我们使用gtk+libvlc实现了一个最简单的视频播放器,可以实现点击按钮暂定和停止播放视频,以及同步显 示视频播放进度,但即使作为一个视频播放器,只有这些功能也还是不够的,至少我们还应该有全屏播放的功能吧,所以这一次我们就来为上一次的视频播放器添加 上全屏播放功能。这个功能实现起来思路很简单,只是具体实现过程中有很多坑罢了,需要我们注意很多细节问题,还要解决一些bug等等。这次我们的代码出了 增加功能之外,也还会对上一次的基础代码做一些修改。

二、功能实现思路
使用gtk提供的API函数,gtk_window_fullscreen可以将窗口设置为全屏,即隐藏标题栏、菜单栏和工具栏,同时隐藏窗口下端的视频 控制条,只保留视频播放绘图构件。在全屏状态时视频的播放控制及进度显示,由另一个单独的浮动弹出式窗体实现。通过点击浮动控制窗体上的退出全屏按钮使用 API函数gtk_window_unfullscreen可以退出全屏状态,此时再还原之前隐藏的正常模式下的控制条构件,及隐藏浮动弹出式窗体

三、要实现的功能点

  • 全屏播放

  • 全屏状态浮动控制条

  • 无鼠标动作时浮动控制条和鼠标指针自动隐藏

  • 鼠标动作激活浮动控制条和鼠标指针

  • 单击播放区域暂停/播放,双击全屏/退出全屏

四、功能点具体实现
1.全屏播放
我们先实现点击全屏按钮进入全屏状态,所以要在之前的底部控制条添加一个全屏按钮,并为其绑定一个点击事件处理函数

full_screen_button = gtk_button_new_from_icon_name("view-fullscreen", GTK_ICON_SIZE_BUTTON);
gtk_box_pack_end(GTK_BOX(hbox), full_screen_button, FALSE, FALSE, 0);

g_signal_connect(full_screen_button, "clicked", G_CALLBACK(on_full_screen), NULL);



on_full_screen事件处理函数如下:

void on_full_screen(GtkWidget *widget, gpointer data)
{
    // 设置正常窗体进入全屏状态
    gtk_window_fullscreen(GTK_WINDOW(window));

    // 设置一个已进入全屏状态的标志,也可以通过GDK获得窗口状态,不过会略麻烦
    is_fullscreen = TRUE;

    // 隐藏窗口底部控制条和顶部菜单栏
    gtk_widget_hide(hbox);
    gtk_widget_hide(menubar);

    // 显示进入全屏状态后的浮动控制条窗体,后面会说明该窗体的具体实现
    gtk_widget_show_all(GTK_WIDGET(ctrl_window));

    // 根据当前播放状态同步进入全屏状态后的按钮图标,原本考虑在点击按钮时同时设置控制条上的按钮图标和浮动控制条上的
    // 但因为始终有一个为隐藏状态,设置就会不成功反而会影响当前的按钮图标,故简单的改在进入全屏状态时单独进行同步
    if(libvlc_media_player_is_playing(media_player) == 1)
        play_icon_p_w_picpath = gtk_p_w_picpath_new_from_icon_name("media-playback-pause", GTK_ICON_SIZE_BUTTON);
    else
        play_icon_p_w_picpath = gtk_p_w_picpath_new_from_icon_name("media-playback-start", GTK_ICON_SIZE_BUTTON);
    gtk_button_set_p_w_picpath(GTK_BUTTON(full_screen_pause_button), play_icon_p_w_picpath);
    ...
    //下面还会有与实现自动隐藏浮动控制条窗体的代码,后面会说明


on_quit_full_screen事件处理函数如下:

void on_quit_full_screen(GtkWidget *widget, gpointer data)
{
    gtk_window_unfullscreen(GTK_WINDOW(window));
    is_fullscreen = FALSE;
    gtk_widget_show(hbox);
    gtk_widget_show(menubar);
    gtk_widget_hide(ctrl_window);
  
    g_signal_handlers_block_by_func(G_OBJECT(player_widget), on_mouse_motion, NULL);
}


2.创建一个浮动控制条窗体
因为我们是要创建一个特殊的窗体即没有边框,考虑这一点可以使用gtk的弹出窗体类型GTK_WINDOW_POPUP来创建,另外我们还需要固定该窗体的大小和位置,其上的构件基本和主窗体底部的控制条基本一致。下面我们单独使用一个函数来创建和初始化该窗体

void control_window_init()
{
    GtkWidget *ctrl_hbox, *quit_full_screen_button, *full_screen_stop_button;

    ctrl_window = gtk_window_new(GTK_WINDOW_POPUP);
    gtk_window_set_position(GTK_WINDOW(ctrl_window), GTK_WIN_POS_CENTER);
    //这里使用了两个宏来设置该窗体的大小
    gtk_window_set_default_size(GTK_WINDOW(ctrl_window), CTRL_WINDOW_WIDTH, CTRL_WINDOW_HEIGHT);
    g_signal_connect(ctrl_window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    ctrl_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, TRUE);
    gtk_container_add(GTK_CONTAINER(ctrl_window), ctrl_hbox);

    full_screen_pause_button = gtk_button_new_from_icon_name("media-playback-pause", GTK_ICON_SIZE_BUTTON);
    full_screen_stop_button = gtk_button_new_from_icon_name("media-playback-stop", GTK_ICON_SIZE_BUTTON);
    quit_full_screen_button = gtk_button_new_from_icon_name("view-restore", GTK_ICON_SIZE_BUTTON);
    gtk_box_pack_start(GTK_BOX(ctrl_hbox), full_screen_pause_button, FALSE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(ctrl_hbox), full_screen_stop_button, FALSE, TRUE, 0);
    gtk_box_pack_end(GTK_BOX(ctrl_hbox), quit_full_screen_button, FALSE, TRUE, 0);
    g_signal_connect(G_OBJECT(full_screen_pause_button), "clicked", G_CALLBACK(on_playpause), NULL);
    g_signal_connect(G_OBJECT(full_screen_stop_button), "clicked", G_CALLBACK(on_stop), NULL);
    g_signal_connect(G_OBJECT(quit_full_screen_button), "clicked", G_CALLBACK(on_quit_full_screen), NULL);

    ctrl_process_scale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, process_adjuest);
    gtk_box_pack_start(GTK_BOX(ctrl_hbox), ctrl_process_scale, TRUE, TRUE, 0);
    gtk_scale_set_draw_value (GTK_SCALE(ctrl_process_scale), FALSE);
    gtk_scale_set_has_origin (GTK_SCALE(ctrl_process_scale), TRUE);
    gtk_scale_set_value_pos(GTK_SCALE(ctrl_process_scale), 0);
    g_signal_connect(G_OBJECT(ctrl_process_scale),"value_changed", G_CALLBACK(on_scale_value_change), NULL);
}






3.设置浮动控制条窗体自动隐藏
还记得我们上一节实现同步进度条时用过的定时器么,g_timeout_add(),这里还是通过它来实现自动隐藏,我们设置超时5s 自动隐藏浮动窗体。但超时要从什么开始计时呢?前面已经说过了嘛,即当我们鼠标没有动作时即开始计时(准确的应该是,计时是循环计时的,我们只是通过判断 鼠标是否在动作来在计时器中设置是否隐藏)
首先我们应该解决如何知道鼠标是否移动,这就又要用到gtk/gdk的事件了,motion_notify_event事件就是在鼠标移动时被触发的,那 么我们可以为play_widget构件绑定该事件的事件处理,但是默认情况下,该构件的motion_notify_event事件是被屏蔽了的(因为 不同的构件功能不同,如果都开启所有事件,那必然会十分混乱),我们就得手动添加该事件,同时我们也开启鼠标点击事件,后面会用来实现单击播放区域暂定等
在主窗体初始化函数中添加(上一节代码的main函数中,这一节使用单独的函数创建和初始化主窗体)

// 添加时间,打开屏蔽事件
gtk_widget_add_events(GTK_WIDGET(player_widget), GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK);
// 绑定事件处理
g_signal_connect(G_OBJECT(player_widget), "motion_notify_event", G_CALLBACK(on_mouse_motion), NULL);
// 在非全屏状态先阻塞该事件处理,进入全屏后再取消阻塞
g_signal_handlers_block_by_func(G_OBJECT(player_widget), on_mouse_motion, NULL);


我们还要在进入全屏状态后在on_full_screen添加定时器


g_signal_handlers_unblock_by_func(G_OBJECT(player_widget), on_mouse_motion, NULL);
g_timeout_add(5000, (GSourceFunc)_hide_ctrl_window, NULL);

on_mouse_motion事件处理函数

void on_mouse_motion(GtkWidget *widget, gpointer data)
{
    // 设置是否移动状态标志,避免在移动过程中定时器超时将浮动窗体和鼠标箭头隐藏
    is_moving = TRUE;
    // 鼠标有动作,恢复隐藏窗体
    gtk_widget_show(GTK_WIDGET(ctrl_window));
    // 恢复鼠标箭头
    gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window)), cur);
    // 重新设置浮动窗体显示位置
    width = gdk_screen_get_width(gdk_screen_get_default());
    height = gdk_screen_get_height(gdk_screen_get_default());
    // 水平屏幕居中,垂直距屏幕最下端50pix
    gtk_window_move(GTK_WINDOW(ctrl_window), (width-CTRL_WINDOW_WIDTH)/2, height-50);
    is_moving = FALSE;
}



_hide_ctrl_window定时器处理函数

gboolean _hide_ctrl_window(gpointer data)
{
    if (!is_fullscreen || is_moving ) {
        return FALSE;
    }
    // 设置鼠标箭头为GDK_BLANK_CURSOR,即空
    gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window)), gdk_cursor_new(GDK_BLANK_CURSOR));
    // 隐藏浮动控制窗体
    gtk_widget_hide(GTK_WIDGET(ctrl_window));
    return TRUE;
}



不过这里还有一个需要注意的地方,你之前可能会发现,默认情况下,即我们没有添加自动隐藏的功能,当我们把鼠标移到播放区域时,一段时间不动它也是会被自 动隐藏的,你可能会觉得这很好啊,那我们就可以省掉gdk_window_set_cursor了。我之前就是这么想的,不过后来发现这里会有问题,如果 单单只是去掉这个鼠标是可以隐藏了,但是它也再不会出现了,除非移出播放显示区。后来发现这是libvlc的问题,vlc它本身也实现了这个鼠标箭头超时 自动隐藏的功能,你使用如下命令会发现,vlc有一个设置这个超时时间的参数

$ vlc --help | grep mouse



但是我却没发现有禁止该功能的参数,所以没办法了,我只能给它设置一个尽可能大的超时时间值来达到禁用的功能。在代码里面我们就需要手动在创建vlc的media_player时指定参数,如下:

const char * const vlc_args[] = {
    //libvlc 鼠标指针自动隐藏事件与gtk的“motion_notify_event”事件有冲突会导致一些问题,
    //故这里设置最大超时时间(相当于禁止vlc的该功能)
    "--mouse-hide-timeout=2147483647",//在 x 毫秒后隐藏光标和全屏控制器
    "--no-xlib"
};
...
vlc_inst = libvlc_new(2 ,vlc_args);
media_player = libvlc_media_player_new(vlc_inst);



4.单击播放区域暂停/播放,双击全屏/退出全屏
这功能不用多解释,基本每个播放器都会实现
前面我们为play_widget添加了点击事件,下面只需为其绑定事件相应的处理处理即可。不过我们如何判断是单击还是双击呢?这需要通过GdkEvent来获得
首先添加事件处理

g_signal_connect(player_widget, "button-press-event", G_CALLBACK(on_play_widget_button_press), NULL);



on_play_widget_button_press函数实现

void on_play_widget_button_press(GtkWidget *widget, GdkEvent *event, gpointer data)
{
    // 判断为单击还是双击
    if (event->button.type == GDK_BUTTON_PRESS){ //单击
        // 直接手动调用前面绑定到暂停按钮的事件处理函数
        on_playpause(widget, data);
    }
    else if(event->button.type == GDK_2BUTTON_PRESS){ //双击
        if(is_fullscreen)
        // 直接手动调用前面绑定到退出全屏按钮的事件处理函数
            on_quit_full_screen(widget, data);
        else
            on_full_screen(widget, data);
    }
}



至此我们就实现了预期的全部功能点。
五、总结
这一节我们又为这个播放器增加了一些功能,但它依然很简单,但相信最为一个学习gtk的入门小项目还是够了,之后就期待感兴趣的用户能自己接着完善这个简易播放器,让其成为一个真正可以作为你日常使用的播放器(以下内容需要在实验楼的虚拟平台上运行,不添加也没有关系,是为了帮助童鞋们更方便学习和理解)

$ git clone -b full_screen https://github.com/shiyanlou/gtk-vlc-video-player.git



演示视频


wget http://anything-about-doc.qiniudn.com/gtk_libvlc_video_player/video_demo_02.mp4

有更多基础课、项目课欢迎大家登陆实验楼官方网站http://www.shiyanlou.com

现在登陆实验楼更有感恩好礼相送http://www.shiyanlou.com/huodong/thanks.html