Thursday, 10 October, 2019 UTC


Summary

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:
  • Angular
  • KendoUI
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:
 npm install @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> <div class="brand"> <img src="/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: 0 1px 2px 0 rgba(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.scss section{ 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.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { 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> <html lang="en"> <head> <meta charset="utf-8"> <title>Angular MediaPlayer</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link href="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.scss html { 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:
 <div class="player"> <video class="player__video viewer" [src]="currentVideo?.source" (timeupdate)="handleProgress()" (ended)="updateButton()" #videoplayer ></video> <div class="player__controls"> <div class="progress" (mousedown)="mouseDown = true" (mouseup)="mouseDown = false" (mouseout)="mouseDown = false" (mousemove)="mouseDown && scrub($event)" (click)="scrub($event)" > <div class="progress__filled" [ngStyle]="progressBarStyle"></div> </div> <div class="controls__secondary"> <div> <i class="player__button toggle k-icon" [ngClass]="iconClass" title="Toggle Play" (click)="togglePlay()" > </i> <i class="player__button next k-icon k-i-arrow-end-right"> </i> </div> <div> <input type="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 the mousedown 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 the mousedown variable to false. Same goes for the mouseout event.
  • mousemove: In the event callback, we check if the mouse is down before scrubbing the video. If mousedown is set to true, we call the seek method.
  • click: On click, we take the user to the selected time in the video using the seek 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'], }) export class MediaplayerComponent implements OnInit { 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: 0 0 20px 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), 0 0 1px 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:0 0 2px 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), 0 0 1px 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: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(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.ts import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-playlist', templateUrl: './playlist.component.html', styleUrls: ['./playlist.component.scss'], }) export class PlaylistComponent implements OnInit { constructor() {} @Input() videos: Array<{}>; @Output() videoClicked = new EventEmitter<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.scss ul { list-style: none; padding-left: 0; margin: 0 0 0 50px; 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.