In this tutorial, we'll see how to combine Kendo UI components and Angular to create a media player application.
Kendo UI is a customizable set of Javascript UI components; it also provides a set of themes for Material UI and Bootstrap, and it comes packed with a unique icon set and a range of colors in its palette. It has libraries for Angular, Vue, React and jQuery, and components available as multiple npm packages, eliminating the fear of bloating your application with unnecessary parts and increasing your build bundle.
Angular is a JavaScript framework for creating a web application; it offers the best practices and tools to ease the development of applications. Angular uses declarative templates, dependency injection and Observables to power applications on several platforms.
Kendo UI provides Angular-specific components for ease of development, and we’ll be using some of them to build a simple media player. The application will make use of the HTML5 video player coupled, and it’ll feature custom controls built using icons from Kendo UI’s icon set.
To follow this tutorial, you need a basic understanding of Angular. Ensure that you have Node and npm installed before you get started.
If you have no prior knowledge of Angular, kindly follow the official tutorial here. You can return to this article when you’re done.
We’ll be building the application using the following tools:
Here’s a screenshot of the application we’ll be building:
Initializing the Application and Installing Dependencies
To get started, we will use the CLI (command line interface) provided by the Angular team to initialize our project.
First, install the CLI by running npm install -g @angular/cli
. npm is a package manager used for installing packages. It will be available on your PC if you have Node installed; if not, download Node here.
To create a new Angular project using the CLI, open a terminal and run:
ng new angular-mediaplayer --style=scss
This command is used to initialize a new Angular project; the project will be using SCSS as the pre-processor.
Next, run the following command in the root folder of the project to install dependencies:
npminstall @progress/kendo-theme-default
Start the Angular development server by running ng serve
in a terminal in the root folder of your project.
Header Component
The header component will display the application logo and very little information. This component is mostly a display component.
Run the following command to create the header component:
ng generate component header
Next, open the src/app/header/header.component.html
file and update it to look like the code below:
<!-- src/app/header/header.component.html --><header><divclass="brand"><imgsrc="/assets/logo.svg"><h5>Just Play</h5></div></header>
Note: Image asset used can be found here in the GitHub repository. Assets are from https://flaticon.com.
Next, we’ll style the header. Open the header.component.scss
file and update it with the snippet below:
header {display: flex;background-color:#f7f7f7;align-items: center;margin:0;padding:20px 5%;box-shadow:01px 2px 0rgba(0, 0, 0, 0.1);.brand {flex:1;display: flex;align-items: center;img {height:35px;border-radius:50%;margin-right:17px;}h5 {font-size:18px;margin:0;text-transform: capitalize;letter-spacing:0.5px;font-weight:600;color:#ff1d5e;opacity:0.7;}}}
Just a couple of styles to beautify the header.
Next, we’ll update the app.component.html
file to render the header.
// src/app/app.component.html
<main><app-header></app-header><section><div><!-- media player comes here --></div><div><!-- video playlist comes here --></div></section></main>
If you visit http://localhost:4200, you should see the latest view of the application. The header is the only visible thing on the page but not for too long. Stay tuned!
Home View
The home page will house the media player, the playlist and the header. The default App
component will house the home page of the application. The app.component.html
file requires no changes; it should stay the same as the previous section.
<main><app-header></app-header><section><div><!-- media player comes here --></div><div><!-- video playlist comes here --></div></section></main>
Next, we’ll add some styles to the app.component.scss
file to give the page a bit more life. Open the app.component.scss
file and copy the styles below into it:
// src/app/app.component.scsssection{display: flex;justify-content: center;margin-top:100px;}
That isn’t much, probably a lot less than you expected, but we’re moving. Next, we’ll define the playlist we’ll display and other variables useful for our application. Open the app.component.ts
file and update it:
// src/app/app.component.tsimport{ Component }from'@angular/core';
@Component({
selector:'app-root',
templateUrl:'./app.component.html',
styleUrls:['./app.component.scss'],})exportclassAppComponent{
playlist =[{
name:'Arial view of roads',
source:'https://player.vimeo.com/external/293373387.sd.mp4?s=546b9073d6ed62a05e064043589e30a8bb3ce6fa&profile_id=164&oauth2_token_id=57447761',
thumbnail:'https://res.cloudinary.com/hackafro/image/upload/c_scale,h_100,w_150/v1554641467/Screenshot_2019-04-07_at_1.39.17_PM_purcgf.png',},{
name:'Blur colorful lights',
source:'https://player.vimeo.com/external/305211631.sd.mp4?s=3d46306a3d07d1c56eb64f1fcb1ba96232e34d90&profile_id=164&oauth2_token_id=57447761',
thumbnail:'https://res.cloudinary.com/hackafro/image/upload/c_scale,h_100,w_150/v1554641309/Screenshot_2019-04-07_at_1.46.12_PM_ztnroy.png',},{
name:'Amazing view of the sunset',
source:'https://player.vimeo.com/external/306619138.sd.mp4?s=a7cb8a56ee700da618a4bc6bdd474eca0cf75d92&profile_id=164&oauth2_token_id=57447761',
thumbnail:'https://res.cloudinary.com/hackafro/image/upload/c_scale,h_100,w_150/v1554641380/Screenshot_2019-04-07_at_1.46.38_PM_f6nyr4.png',},{
name:'Lighthouse by the sea',
source:'https://player.vimeo.com/external/312662160.sd.mp4?s=22154e69be5722a528e3c1cc374250af726a2b44&profile_id=164&oauth2_token_id=57447761',
thumbnail:'https://res.cloudinary.com/hackafro/image/upload/c_scale,h_100,w_150/v1554641395/Screenshot_2019-04-07_at_1.46.26_PM_xgbfdq.png',},];
currentVideo =this.playlist[0];onVideoChange(video){this.currentVideo = video;}}
NB: Video assets are from https://pexels.com and video thumbnails are stored in Cloudinary.
In the snippet above, we declared a playlist
array that contains videos we’ll be displaying. Each item in the array contains the source
, thumbnail
and name
details for the video.
There’s the onVideoChange
method acting as an event handler; this method runs when an item in the playlist is selected; we set the clicked video as the currentVideo
.
Next, we’ll include the external assets the application will be using. We’ll be using the Roboto
font. Also, we’ll update style.scss
and the angular.json
file to include the Kendo UI component stylesheets and application-wide styles.
Open the index.html
file and include the link to the external fonts:
<!doctype html><htmllang="en"><head><metacharset="utf-8"><title>Angular MediaPlayer</title><basehref="/"><metaname="viewport"content="width=device-width, initial-scale=1"><linkrel="icon"type="image/x-icon"href="favicon.ico"><linkhref="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900"rel="stylesheet"></head><body><app-root></app-root></body></html>
Then open the style.scss
file copy the styles below into it:
// styles.scsshtml {box-sizing: border-box;}*, *:before, *:after {box-sizing: inherit;}body {margin:0;padding:0;min-height:100vh;}
To include Kendo UI’s base stylesheet in our project, we’ll add it to the styles array in the angular.json
file. Open the file and update the styles
array with the base CSS
file from Kendo UI.
{"$schema":"./node_modules/@angular/cli/lib/config/schema.json","version":1,"newProjectRoot":"projects","projects":{"angular-mediaplayer":{...},"architect":{"build":{"builder":"@angular-devkit/build-angular:browser","options":{..."styles":["src/styles.scss","node_modules/@progress/kendo-theme-default/dist/all.css"],"scripts":[]},// ... rest of the configuration}
Next, we’ll begin working on the media player component. The component will feature the HTML5 video player with custom controls.
Media Player Component
Using the media player component, we’ll play the active video in the playlist. To create the media player component, run the following command:
ng generate component mediaplayer
After running the command, open the mediaplayer.component.html
file and copy the content below into it:
<divclass="player"><videoclass="player__video viewer"[src]="currentVideo?.source"(timeupdate)="handleProgress()"(ended)="updateButton()"#videoplayer></video><divclass="player__controls"><divclass="progress"(mousedown)="mouseDown = true"(mouseup)="mouseDown = false"(mouseout)="mouseDown = false"(mousemove)="mouseDown && scrub($event)"(click)="scrub($event)"><divclass="progress__filled"[ngStyle]="progressBarStyle"></div></div><divclass="controls__secondary"><div><iclass="player__button toggle k-icon"[ngClass]="iconClass"title="Toggle Play"(click)="togglePlay()"></i><iclass="player__button next k-icon k-i-arrow-end-right"></i></div><div><inputtype="range"name="volume"class="player__slider"min="0"max="1"step="0.05"value="1"(change)="handleVolumeChange($event)"/></div></div></div></div>
In the snippet above, you’ll probably notice the high amount of event handler attached to elements in the file; we’ll go through them and explain what each event does.
On the video
element, we receive the video’s source from the App
component; also, we listen for two events: timeupdate
and ended
. The timeupdate
event is triggered for the duration of the video’s play time, as each second of the video goes by the event is triggered. The ended
event is triggered when the video has finished playing; I’m sure you guessed that.
Next, there’s the progress
element; this element will act as the progress bar for the video, it’ll display the current play time of the video, we’ll also use this element to scrub the video, jumping between times in the video. Attached to the element are four events:
mousedown
: When this event is triggered, we set themousedown
event to true; this is set to true because we only want to scrub the video when the user’s mouse is down.mouseup
: Here, we set themousedown
variable tofalse
. Same goes for themouseout
event.mousemove
: In the event callback, we check if the mouse is down before scrubbing the video. Ifmousedown
is set totrue
, we call theseek
method.click
: On click, we take the user to the selected time in the video using theseek
method.
From here, we have the play button; this is used to toggle the video’s playing state as signified in the element’s click listener.
Next, we’ll update the mediaplayer.component.ts
file to declare the variables and methods used in the view template:
import{ Component, OnInit, ViewChild, Input }from'@angular/core';
@Component({
selector:'app-mediaplayer',
templateUrl:'./mediaplayer.component.html',
styleUrls:['./mediaplayer.component.scss'],})exportclassMediaplayerComponentimplementsOnInit{constructor(){}
@ViewChild('videoplayer') videoElement;
@Input() currentVideo;
video: HTMLVideoElement;
progressBarStyle ={
flexBasis:'0%',};
iconClass ='k-i-play';
mouseDown =false;togglePlay(){const method =this.video.paused ?'play':'pause';this.video[method]();setTimeout(()=>this.updateButton(),10);}updateButton(){const icon =this.video.paused ?'k-i-play':'k-i-pause';this.iconClass = icon;}handleVolumeChange(e){const{ target }= e;const{ value }= target;this.video.volume = value;}handleProgress(){const percent =(this.video.currentTime /this.video.duration)*100;this.progressBarStyle.flexBasis =`${percent}%`;}seek(e: MouseEvent){const{ srcElement: progress, offsetX }= e;const{ offsetWidth }= progress;const seekTime =(offsetX / offsetWidth)*this.video.duration;this.video.currentTime = seekTime;}ngOnInit(){const{ nativeElement }=this.videoElement;this.video = nativeElement;}ngOnChanges(changes){if(this.video){this.progressBarStyle.flexBasis ='0%';this.togglePlay();}}}
The component file is littered with event handlers and component lifecycle methods and we’ll go through each method, starting easy before moving to the complex methods.
The togglePlay
method seems like an easy enough start. In the method, we check if the paused
property is true, then we call the exact opposite method of the element. If paused
, we call video.play()
, and vice versa.
Also within the togglePlay
method is the updateButton
method that updates the icon displayed on the play button. We also used Kendo UI’s icon set to define the play button. Kendo UI has a rich set of icons that are available here. They are easy to configure and customize.
The handleVolumeChange
method is an event handler for a change
event on the input element we set up for handling the volume
of the video. After every update, we set the value to the video’s volume
property.
The handleProgress
method is a handler for the timeupdate
event. Within the method, we divide the video’s duration
by the currentTime
and multiply by one hundred to get the percentage of time played, and we set that to the progress element’s flexBasis
style property.
In the seek
method, we get the position the user clicked by dividing the offsetWidth
(width of an element, including paddings and borders) of the progress bar by the offsetX
(where the user clicked) event value. We multiply the result of the division by the video duration, resulting in the exact time in the video the user intends to skip to; we set that value as the currentTime
of the video.
In the ngOnInit
lifecycle, we get the nativeElement
(video reference) object and assign it to the video
property, and then we listen for changes to the MediaPlayer
component’s Input
properties in the ngOnChanges
lifecycle method.
You might need to go through those method descriptions over again, but don’t worry, the rest of the article awaits you.
Next, we’ll add some styles to give life to the video element and the custom controls we created. Open the mediaplayer.component.scss
file and copy the following styles into it:
// src/app/mediaplayer/mediaplayer.component.scss.player {max-width:750px;border:5px solid rgba(0,0,0,0.2);box-shadow:0020px rgba(0,0,0,0.2);position: relative;font-size:0;overflow: hidden;}.player__video {width:800px;}.player__button {background: none;border:0;line-height:1;color: white;text-align: center;outline:0;padding:0;cursor: pointer;max-width:50px;}.player__button:focus {border-color:#ff1d5e;}.player__slider {width:10px;height:30px;}.player__controls {display: flex;flex-direction: column;position: absolute;bottom:0;width:100%;transform:translateY(100%)translateY(-5px);transition: all .3s;flex-wrap: wrap;align-items: center;background:rgba(0,0,0,0.1);}.controls__secondary{display: flex;justify-content: space-between;width:98%;align-items: center;.toggle{font-size:16px;margin-right:7px;}.next{font-size:25px;}}.player:hover .player__controls {transform:translateY(0);}.player:hover .progress {height:8px;}.progress {flex:4!important;width:98%;margin:0 auto;position: relative;display: flex;flex-basis:100%;height:5px;transition: height 0.3s;background:rgba(167, 165, 165, 0.1);cursor: ew-resize;}.progress__filled {width:50%;background:#ff1d5e;flex:0;flex-basis:50%;}/* unholy css to style input type="range" */input[type=range] {-webkit-appearance: none;background: transparent;width:100%;}input[type=range]:focus {outline: none;}input[type=range]::-webkit-slider-runnable-track {width:100%;height:8.4px;cursor: pointer;box-shadow:1px 1px 1px rgba(0, 0, 0, 0), 001px rgba(13, 13, 13, 0);background:rgba(255,255,255,0.8);border-radius:1.3px;border:0.2px solid rgba(1, 1, 1, 0);}input[type=range]::-webkit-slider-thumb {height:15px;width:15px;border-radius:50px;background:#ff1d5e;cursor: pointer;-webkit-appearance: none;margin-top: -3.5px;box-shadow:002px rgba(0,0,0,0.2);}input[type=range]:focus::-webkit-slider-runnable-track {background:#ec83a1;}input[type=range]::-moz-range-track {width:100%;height:8.4px;cursor: pointer;box-shadow:1px 1px 1px rgba(0, 0, 0, 0), 001px rgba(13, 13, 13, 0);background:#ffffff;border-radius:1.3px;border:0.2px solid rgba(1, 1, 1, 0);}input[type=range]::-moz-range-thumb {box-shadow:000rgba(0, 0, 0, 0), 000rgba(13, 13, 13, 0);height:15px;width:15px;border-radius:50px;background:#ff1d5e;cursor: pointer;}
After this, we’ll render the component in the app.component.html
file and run to the browser to see the latest changes. Open the app.component.html
file and update it to render the mediaplayer
component:
<!-- src/app/app.component.scss -->
<main>
<app-header></app-header>
<section>
<div>
<app-mediaplayer [currentVideo]="currentVideo"></app-mediaplayer>
</div>
<div>
<! -- playlist component here -->
</div>
</section>
</main>
We render the media player component and then pass the currentVideo
property to it. Navigate to http://localhost:4200 to see the component in full force:
Next, we’ll create the playlist area to allow users to go through a selection of videos.
Playlist Component
To create the playlist
component, run the following command:
ng generate component playlist
Open the playlist.component.html
file and copy the content below into it:
<ul><li*ngFor="let video of videos"><img[src]="video.thumbnail"[alt]="video.name"[ngClass]="{ active: currentVideo.name === video.name }"(click)="onVideoClick(video)"/><h5>{{ video.name }}</h5></li></ul>
The component will display a list of video selections for the user to browse through. On the click of a video, we update the source of the video player to that of the clicked video. To implement this functionality, we’ll update the playlist.component.ts
file:
// src/app/playlist/playlist.component.tsimport{ Component, OnInit, Input, EventEmitter, Output }from'@angular/core';
@Component({
selector:'app-playlist',
templateUrl:'./playlist.component.html',
styleUrls:['./playlist.component.scss'],})exportclassPlaylistComponentimplementsOnInit{constructor(){}
@Input() videos:Array<{}>;
@Output() videoClicked =newEventEmitter<Object>();
currentVideo ={};onVideoClick(video){this.videoClicked.emit(video);this.currentVideo = video;}ngOnInit(){this.currentVideo =this.videos[0];}}
The playlist
component will receive an array of videos from the App
component; it’ll also emit a videoClicked
event on click of a video. In the onInit
component, we set the currentVideo
to the first item in the array.
Next, we’ll add some styles to the component to remove the default ul
list style. Open the playlist.component.scss
file and copy the following styles into it:
// src/app/playlist/playlist.component.scssul {list-style: none;padding-left:0;margin:00050px;li {display: flex;margin-bottom:15px;img {width:150px;height:100px;margin-right:10px;cursor: pointer;}h5 {font-weight:500;font-size:15px;text-transform: capitalize;}}li > img.active {border:3px solid #ff1d5e;}}
The next step is to render the component in the app.component.html
file. Open the file and add the playlist component:
<main><app-header></app-header><section><div><app-mediaplayer[currentVideo]="currentVideo"></app-mediaplayer></div><div><app-playlist[videos]="playlist"(videoClicked)="onVideoChange($event)"></app-playlist></div></section></main>
After the changes above, navigate to http://localhost:4200 to view the changes made. The playlist component should be visible on the page and clicking on any video will update the video player source to that of the selected video.
Conclusion
In this tutorial, we’ve seen how easy it is to get started using Kendo UI components for Angular. We’ve also seen how we can utilize the HTML5 API to create a video player with custom controls. You can take this demo one step further by adding previous and next buttons. With this, the user can move to the following video on the playlist. You can find the source code for this demo application here. Happy coding.