Let's continue pur story about creating our own media player.

You can find a demo of the resulting videoPlayer on this page: demo of the videoPlayer - part 3.

What will we do in our third part ? :

  1. Add a control bar to the videoPlayer
  2. Display the progess of the video in this bar
  3. Add a play/pause button in this bar
  4. Enjoy ourselves!



Add a control bar to the videoPlayer

The goal is to display a control bar when the mouse is hover the video or when the media is paused.

The control bar will look like this : Video player how-to: the control bar.

First we create the gray rectangle which is behind the meta-information and the two differents text. We will add them to a group to ease the use of trigger to show/hide them.

/** ###########################################
    * The control bar
    *   ########################################### */
 
var controlBarBackGroundRect : Rectangle = Rectangle{
        x:x
        y: vidHeight + y - 40
        width:vidWith
        height: 40
        opacity:0.5
        fill:Color.DARKGRAY
    }
 
    var playPauseGroupWidth = 20 ;
    var pauseGroup : Group = Group{content:[]};
    var playGroup : Group = Group{content:[]};
    var progressNode  = Group{content:[]};
 
 
    var groupControlBar: Group = Group{
        content: [controlBarBackGroundRect,progressNode, pauseGroup, playGroup]
        cursor: Cursor.DEFAULT
        opacity: 0.0
}


Don't forget to add it in the returned Group of our component:

return Group{
            content: [mediaView,groupVidMETA, groupControlBar]
        }


What you can notice is that we have already prepare the differents things which will be putted in : the playButton, the pauseButton, the progressNode and the grayRectangle which is the bacground ofthe controlbar.

Display the progess of the video in this bar

To display the progress of the media we will create a Progress's node. To create this progress Node we will uses the james weaver's how-to which is on this page : Progress Indicator: Creating a JavaFX Custom Node and Binding to a Model but we will customize it a little to fits our specifications.

Here is a summary of the changes :

  • No more polygon for the progression but a good old Rectangle,
  • Add left, right and center text.

And here is the code :

/*
 *  ProgressNode.fx -
 *  A custom node that functions as a progress bar
 *  TODO: Add the ability to have an "infinite progress" look as well
 *
 *  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
 *  to demonstrate how to create custom nodes in JavaFX
 *
 *  Used and changed 2008 by Jonathan ANTOINE to use in his videoPlayer
 */
 
package fr.antoinej.tools;
 
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Paint;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
 
public class ProgressNode extends CustomNode {
 
    /*
    * A number from 0.0 to 1.0 that indicates the amount of progress
     */
    public var progress:Number;
 
    /*
    * The fill of the progress part of the progress bar.  Because
     * this is of type Paint, a Color or gradient may be used.
     */
    public-init var progressFill:Paint =   LinearGradient {
        startX: 0.0
        startY: 0.0
        endX: 0.0
        endY: 1.0
        stops: [
            Stop {
                offset: 0.0
                color: Color.rgb(0, 192, 255)
            },
            Stop {
                offset: 0.20
                color: Color.rgb(0, 172, 234)
            },
            Stop {
                offset: 1.0
                color: Color.rgb(0, 112, 174)
            },
        ]
    };
 
    /*
    * The fill of the bar part of the progress bar. Because
     * this is of type Paint, a Color or gradient may be used.
     */
    public-init  var barFill:Paint = LinearGradient {
        startX: 0.0
        startY: 0.0
        endX: 0.0
        endY: 1.0
        stops: [
            Stop {
                offset: 0.0
                color: Color.rgb(112, 112, 112)
            },
            Stop {
                offset: 1.0
                color: Color.rgb(88, 88, 88)
            },
        ]
    };
    /*
    * The color of the progress percent text on the progress bar
     */
    public-init var progressPercentColor:Color = Color.rgb(191, 223, 239);
 
    /*
    * The color of the progress text on the right side of the progress bar
     */
    public-init var progressTextColor:Color = Color.WHITE;
 
    /*
    * Determines the width, in pixels, of the progress bar
     */
    public-init var width:Integer = 200;
 
    public var leftStr : String = "";
    public var rightStr : String = "";
    public var centerStr : String = "";
 
    /*
    * Determines the height, in pixels, of the progress bar
     */
    public-init var height:Integer = 20;
 
    /**
    * Create the Node
     */
    public override function create():Node {
        var textRef:Text;
        var progBarFont =Font.font("Verdana", FontWeight.EXTRA_BOLD, 10);
        var leftAndRightFont =Font.font("Sans serif", FontWeight.EXTRA_BOLD, 10);
        return Group{
 
            content: [
                // The entire progress bar
                Rectangle {
                    x:0
                    y:0
                    width: bind width
                    height: bind height
                    fill: bind barFill
                },
                Rectangle {
                    x:0
                    y:0
                    width: bind progress * width
                    height: height
                    fill: bind progressFill
                }
              ,
                // The percent complete displayed on the progress bar
                textRef = Text {
                    translateX: width / 2 - 20
                    translateY: 5
                    textOrigin: TextOrigin.TOP
                    font: progBarFont
 
                    fill: bind progressPercentColor
                    content: bind "{centerStr}({progress * 100 as Integer}%)"
                },
                Text {
                    translateX: bind width  - rightStr.length() * 6
                    translateY: 5
                    textOrigin: TextOrigin.TOP
                    font: leftAndRightFont
                    fill: bind progressPercentColor
                    content: bind "{rightStr}"
                },
                Text {
                    translateX: 4
                    translateY: 5
                    textOrigin: TextOrigin.TOP
                    font: leftAndRightFont
                    fill: bind progressPercentColor
                    content: bind "{leftStr}"
                }
 
            ]
        }    
 
    }
 
 
}



Now we gonna add it to our videoPlayer changing the precedent progressNode declaration by this one :

var progressNode: ProgressNode =
    ProgressNode{
        translateX:controlBarBackGroundRect.x + 10 + playPauseGroupWidth
        translateY:controlBarBackGroundRect.y + 10
        progress: bind mediaPlayer.currentTime.toMillis() / media.duration.toMillis()
        centerStr: bind "{%tM mediaPlayer.currentTime.toDate()}:{%tS mediaPlayer.currentTime.toDate()}"
        height:20
        width: vidWith - 20 - playPauseGroupWidth
        leftStr:"0:00${media.duration}"
        rightStr: bind "{%tM media.duration.toDate()}:{%tS media.duration.toDate()}"
    };

Note that we bind the rightStr to fit the sun usages given on this page.



Add a play/pause button in this bar

Now we gonna add a play/pause button. To do this we are going to create two groups, a play group which will be visible when the video is paused and a pause group visible when the video is played.

To create this buttons we are using polygons whe re some may prefers image: that's our choice !


Here is the code :

var pauseGroup : Group = Group{
        var rect1 =Rectangle {
            x: controlBarBackGroundRect.x + 7
            y: controlBarBackGroundRect.y + 13
            width: 4
            height: 13
            fill: Color.WHITE
            } ;
        var rect2 = Rectangle {
            x: controlBarBackGroundRect.x + 14
            y: controlBarBackGroundRect.y + 13
            width: 4
            height: 13
            fill: Color.WHITE
            };
        content: [rect1,rect2 ,
            Rectangle {
                x: controlBarBackGroundRect.x + 4
                y: controlBarBackGroundRect.y + 11
                width: 17
                height: 16
                fill: Color.TRANSPARENT
            }
        ]
        cursor:Cursor.HAND
        //On left click, play or pause the video
        onMouseClicked: function(me: MouseEvent){
            //Only a left click play/pause the video
            if(me.button == MouseButton.PRIMARY) {
                mediaPlayer.pause();
            }
        }
        onMouseEntered: function(me: MouseEvent){
            Timeline {
                keyFrames: [
               at(0s) {
                    rect1.fill => rect1.fill  tween Interpolator.LINEAR;
                    rect2.fill => rect1.fill  tween Interpolator.LINEAR;
              }
                  at(800ms) {
                    rect1.fill => Color.RED  tween Interpolator.LINEAR;
                    rect2.fill => Color.RED tween Interpolator.LINEAR;
            }
                ]
            }.play();
 
        }
        onMouseExited: function(me: MouseEvent){
            Timeline {
                keyFrames: [
               at(0s) {
                    rect1.fill => rect1.fill  tween Interpolator.LINEAR;
                    rect2.fill => rect1.fill  tween Interpolator.LINEAR;
              }
                  at(800ms) {
                    rect1.fill => Color.WHITE  tween Interpolator.LINEAR;
                    rect2.fill => Color.WHITE  tween Interpolator.LINEAR;
            }
                ]
            }.play();
 
        }
 
        visible: bind (mediaPlayer.status == mediaPlayer.PLAYING)
}
 
    var playGroup : Group = Group{
        var polygon = Polygon {
            points: [
                    controlBarBackGroundRect.x + 7,controlBarBackGroundRect.y + 14 ,
                    controlBarBackGroundRect.x + 7,controlBarBackGroundRect.y + 27,
                    controlBarBackGroundRect.x + 20,controlBarBackGroundRect.y + 20 ]
            fill: Color.WHITE
           };
        content: [polygon ,
            Rectangle {
                x: controlBarBackGroundRect.x + 4
                y: controlBarBackGroundRect.y + 11
                width: 17
                height: 16
                fill: Color.TRANSPARENT
            }
        ]
        cursor:Cursor.HAND
        //On left click, play or pause the video
        onMouseClicked: function(me: MouseEvent){
            //Only a left click play/pause the video
            if(me.button == MouseButton.PRIMARY) {
                mediaPlayer.play();
            }
        }
        onMouseEntered: function(me: MouseEvent){
            Timeline {
                keyFrames: [
               at(0s) {
                    polygon.fill => polygon.fill  tween Interpolator.LINEAR;
                             }
                  at(800ms) {
                    polygon.fill => Color.RED  tween Interpolator.LINEAR;
                             }
                ]
            }.play();
 
        }
        onMouseExited: function(me: MouseEvent){
            Timeline {
                keyFrames: [
               at(0s) {
                    polygon.fill => polygon.fill  tween Interpolator.LINEAR;
                              }
                  at(800ms) {
                    polygon.fill => Color.WHITE  tween Interpolator.LINEAR;
                           }
                ]
            }.play();
 
        }
 
        visible: bind (mediaPlayer.status == mediaPlayer.PAUSED)
}



You can find our component source in this post and view a demo on this page: demo of the videoPlayer - part 3.