Howler.js Ionic & Capacitor Media Controller - ionic-framework

I am writing an application with ionic using "Howler.js" and "Capacitor Music Controls Plugin". With "howler.js", I manage in-app audio files as I want, but I want to add media control feature on statusbar when the application continues to run in the background. As I mentioned, as a result of my research, I found the "Capacitor Music Controls Plugin" and successfully included it in the project. If we come to my problem; Although I can "play / pause" in the app, I pause on the statusbar, but when I press play again it tries to play the same audio file over and over. I share the functions I have written and used below.
Note: As far as I check it on the console, it calls "CapacitorMusicControls" more than once. You can see it in detail below.
https://ibb.co/Ld80LNX
https://ibb.co/6sPcmsd
https://ibb.co/Cs9Nw2L
https://ibb.co/Bj0h9fP
https://ibb.co/2hhqhQW
import { Injectable } from '#angular/core';
import { Howl } from 'howler';
import { Plugins } from '#capacitor/core';
const { CapacitorMusicControls } = Plugins;
#Injectable({
providedIn: 'root'
})
export class GeneralService {
public detail: string;
public activeTrack;
player: Howl = null;
public isPlaying = false;
public progress = 0;
public durationSongHour = Number();
public durationSongMin = Number();
public durationSongSec = Number();
public currentTime = Number();
public currentTimeHour = Number();
public currentTimeSec = Number();
public currentTimeMin = Number();
public durationSong = Number();
public playlist: any;
constructor(
) {
CapacitorMusicControls.addListener('controlsNotification', (info: any) => {
console.log('listener', info.message);
this.handleControlsEvent(info);
});
}
public createMusicControl(track) {
CapacitorMusicControls.create({
track: track.title, // optional, default : ''
artist: track.subtitle, // optional, default : ''
cover: track.img, // optional, default : nothing
// cover can be a local path (use fullpath 'file:///storage/emulated/...', or only 'my_image.jpg' if my_image.jpg is in the www folder of your app)
// or a remote url ('http://...', 'https://...', 'ftp://...')
// hide previous/next/close buttons:
hasPrev: true, // show previous button, optional, default: true
hasNext: true, // show next button, optional, default: true
hasClose: true, // show close button, optional, default: false
// iOS only, optional
duration: 60, // optional, default: 0
elapsed: 10, // optional, default: 0
hasSkipForward: true, //optional, default: false. true value overrides hasNext.
hasSkipBackward: true, //optional, default: false. true value overrides hasPrev.
skipForwardInterval: 15, //optional. default: 15.
skipBackwardInterval: 15, //optional. default: 15.
hasScrubbing: false, //optional. default to false. Enable scrubbing from control center progress bar
// Android only, optional
isPlaying: true, // optional, default : true
dismissable: true, // optional, default : false
// text displayed in the status bar when the notification (and the ticker) are updated
ticker: 'Now playing "Time is Running Out"',
//All icons default to their built-in android equivalents
//The supplied drawable name, e.g. 'media_play', is the name of a drawable found under android/res/drawable* folders
playIcon: 'media_play',
pauseIcon: 'media_pause',
prevIcon: 'media_prev',
nextIcon: 'media_next',
closeIcon: 'media_close',
notificationIcon: 'notification'
}, (res) => {
console.log('res: ' + res);
}, (err) => {
console.log('err: ' + err);
});
}
public start(track) {
if (this.player) {
CapacitorMusicControls.destroy();
this.player.stop();
}
this.player = new Howl({
src: [track.path],
html5: true,
onplay: () => {
console.log('onplay');
this.detail = track;
this.isPlaying = true;
this.createMusicControl(track);
this.activeTrack = track.id;
this.durationSong = this.player.duration();
this.durationSongHour = Math.floor(this.durationSong / 3600);
this.durationSongMin = Math.floor(this.durationSong % 3600 / 60);
this.durationSongSec = Math.floor(this.durationSong % 3600 % 60);
this.acurrenTime();
this.updateProgress();
},
onend: () => {
this.nextSongAfterFinished();
}
});
this.player.play();
}
public tooglePlayer(pause) {
if (pause == "pause") {
this.player.pause();
this.isPlaying = false;
} else if (pause == "play") {
this.player.play();
this.isPlaying = true;
} else {
this.player.pause();
this.isPlaying = false;
}
}
public nextSong(playlist) {
var index = playlist.findIndex(x => x.id === this.activeTrack);
if (index != playlist.length - 1) {
this.start(playlist[index + 1]);
} else {
this.start(playlist[0]);
}
}
public nextSongAfterFinished() {
console.log('sıradaki sıradaki', this.playlist);
var index = this.playlist.findIndex(x => x.id === this.activeTrack);
if (index != this.playlist.length - 1) {
this.start(this.playlist[index + 1]);
} else {
this.start(this.playlist[0]);
}
}
public prevSong(playlist) {
var index = playlist.findIndex(x => x.id === this.activeTrack);
if (index > 0) {
this.start(playlist[index - 1]);
} else {
this.start(playlist[playlist.length - 1]);
}
}
public acurrenTime() {
this.currentTime = this.player.seek();
this.currentTimeHour = Math.floor(this.currentTime / 3600);
this.currentTimeMin = Math.floor(this.currentTime % 3600 / 60);
this.currentTimeSec = Math.floor(this.currentTime % 3600 % 60);
setTimeout(() => {
this.currentTimeHour = Math.floor(this.currentTime / 3600);
this.currentTimeMin = Math.floor(this.currentTime % 3600 / 60);
this.currentTimeSec = Math.floor(this.currentTime % 3600 % 60);
this.acurrenTime();
}, 1000)
}
public seek(range) {
let duration = this.player.duration();
this.player.seek(duration * (range / 100));
}
public updateProgress() {
let seek = this.player.seek();
this.progress = (seek / this.player.duration()) * 100 || 0;
setTimeout(() => {
this.updateProgress();
}, 1000)
}
handleControlsEvent(action) {
const message = action.message;
console.log("message: " + message)
switch (message) {
case 'music-controls-next':
//do something
break;
case 'music-controls-previous':
//do something
break;
case 'music-controls-pause':
this.isPlaying = false;
this.player.pause();
break;
case 'music-controls-play':
this.isPlaying = true;
this.player.play();
break;
case 'music-controls-destroy':
this.player.stop();
this.activeTrack = null;
CapacitorMusicControls.destroy();
console.log('destroyed', message);
break;
// External controls (iOS only)
case 'music-controls-toggle-play-pause':
// do something
break;
case 'music-controls-seek-to':
// do something
break;
case 'music-controls-skip-forward':
// Do something
break;
case 'music-controls-skip-backward':
// Do something
break;
// Headset events (Android only)
// All media button events are listed below
case 'music-controls-media-button':
this.player.pause();
break;
case 'music-controls-headset-unplugged':
// Do something
break;
case 'music-controls-headset-plugged':
this.player.pause();
break;
default:
break;
}
}
}

Related

I cannot get the html doc to load the javascript to make my calculator working

I've followed this calculator video on YouTube. I have 3 files in a folder .html, .css & .js.
I open the calculator by double-clicking the HTML file which seems to load up fine in opera with the CSS changing the appearance, however the functionality of the buttons do not work at all. Now I have double-checked the code and even gone as far as cheating with the resource material to be sure it's not an error in the code. I feel that the JavaScript isn't being read/used at all when it loads up.
https://www.youtube.com/watch?v=j59qQ7YWLxw
class calculator {
constructor(perviousOperandTextElement, currentOperandTextElement) {
this.perviousOperandTextElement = perviousOperandTextElement
this.currentOperandTextElement = currentOperandTextElement
this.clear()
}
clear() {
this.currentOperand = ''
this.perviousOperand = ''
this.operation = undefined
}
delete() {
this.currentOperand = this.currentOperand.toString().slice(0, -1)
}
appendNumber(number) {
if (number === '.' && this.currentOperand.includes('.')) return
this.currentOperand = this.currentOperand.toString() + number.tostring()
}
chooseOperation(operation) {
if (this.currentOperand === '') return
if (this.perviousOperand !== '') {
this.compute()
}
this.operation = operation
this.perviousOperand = this.currentOperand
this.currentOperand = ''
}
compute() {
let computation
const prev = parseFloat(this.previousOperand)
const current = parseFloat(this.currentOperand)
if (isNaN(prev) || isNaN(current)) return
switch (this.operation) {
case'+':
computation = prev + current
break
case '-':
computation = prev - current
break
case '*':
computation = prev * current
break
case '÷':
computation = prev / current
break
default:
return
}
this.currentOperand = computation
this.operation = undefined
this.perviousOperand = ''
}
getDisplayNumber(number) {
const stringNumber = number.toString()
const integerDigits = parseFloat(stringNumber.split('.')[0])
const decimalDigits = stringNumber.split('.')[1]
let integerDisplay
if (isNaN(integerDigits)) {
integerDisplay = ''
} else {
integerDisplay integerDigits.toLocaleString('en', { maximumFractionDigits: 0 })
}
if (decimalDigits != null) {
return `${integerDisplay}.${decimalDigits}`
} else {
return integerDisplay
}
}
updateDisplay() {
this.currentOperandTextElement.innerText =
this.getDisplayNumber(this.currentOperand)
if (this.operation != null) {
this.previousOperandTextElement.innerText =
`${this.getDisplayNumber(this.perviousOperand)} ${this.operation}`
} else {
this.previousOperandTextElement.innerText = ''
}
}
}
const numberButtons = document.querySelectorAll('[data-number]')
const operationButtons = document.querySelectorAll('[data-operation]')
const equalsButton = document.querySelector('[data-equals]')
const deleteButton = document.querySelector('[data-delete]')
const allClearButton = document.querySelector('[data-all-clear]')
const previousOperandTextElement = document.querySelector('[data-previous-operand]')
const currentOperandTextElement = document.querySelector('[data-current-operand]')
const calculator = new calculator(previousOperandTextElement, currentOperandTextElement)
numberButtons.forEach(button => {
button.addEventListener('click', () => {
calculator.appendNumber(button.innerText)
calculator.updateDisplay()
})
})
operationButtons.forEach(button => {
button.addEventListener('click', () => {
calculator.chooseOperation(button.innerText)
calculator.updateDisplay()
})
})
equalsButton.addEventListener('click', button => {
calculator.compute()
calculator.updateDisplay()
})
allClearButton.addEventListener('click', button => {
calculator.clear()
calculator.updateDisplay()
})
deleteButton.addEventListener('click', button => {
calculator.delete()
calculator.updateDisplay()
})
Your javascript code is wrong at line 67.
We can see that at line 67, your code writes
integerDisplay integerDigits.toLocaleString('en', { maximumFractionDigits: 0 })
This is clearly wrong as you are missing a =, which assigns the value. A solution to this is to replace the line with
integerDisplay = integerDigits.toLocaleString('en', { maximumFractionDigits: 0 })

Updating data doesnt expand the data tree inside material-table

Im trying to build a table with nested tree folder inside.
When trying to add nested data into the datasource data the structure will not updated and will not toggle anymore.
Code below:
https://stackblitz.com/edit/angular-table-tree-example-k2zqmt?file=app%2Ftable-basic-example.ts&file=app%2Ftable-basic-example.html,app%2Ftable-basic-example.ts
Environment
Angular:
Material Table
Material tree system
These are the things that are happening when logNode method is called
The item is getting added but the treeControl.toggle method does not work anymore.
When you are assigning a new dataset to the dataSource all the nodes get reset and the tree closes, so this.treeControl.toggle is trying to toggle a node that does not exist.
You need to find the node to be toggled from the list you get from treeControl.dataNodes
I would suggest having the toggle code in a separate method and adding a node code in a separate method, and a separate button to add the node.
The below code should work for your scenario, also remove this line from your HTML, (click)="treeControl.toggle(data)"
interface ExampleFlatNode {
expandable: boolean;
RoleName: string;
Access: boolean;
level: number;
CatId: number;
}
private transformer = (node: FoodNode, level: number) => {
return {
expandable:
!!node.CategoryPermissions && node.CategoryPermissions.length > 0,
RoleName: node.RoleName,
Access: node.Access,
level: level,
CatId: node.CatId,
};
};
tempNodes = []
constructor() {
this.dataSource.data = TREE_DATA;
}
logNode(clickedNode) {
this.tempNodes = [];
this.treeControl.dataNodes.forEach((node) =>
this.tempNodes.push({
...node,
expanded: this.treeControl.isExpanded(node),
})
);
if (!this.treeControl.isExpanded(clickedNode)) {
const temp = {
Access: true,
RoleName: 'test 1 2',
CatId: 113,
};
const clickedNodeIdx = this.treeControl.dataNodes.findIndex(
(node: any) =>
node.CatId === clickedNode.CatId &&
node.RoleName === clickedNode.RoleName &&
node.level === clickedNode.level
);
const childIdx = 1;
let child;
if (clickedNode.level === 0) {
child =
this.dataSource.data[clickedNodeIdx].CategoryPermissions[childIdx];
} else {
this.dataSource.data.forEach(
(item) => (child = this.findDataSource(item, clickedNode))
);
}
child.CategoryPermissions.push(temp);
this.dataSource.data = this.dataSource.data;
const addedNode = this.treeControl.dataNodes.find(
(node: any) =>
node.CatId === temp.CatId && node.RoleName === temp.RoleName
);
this.expandParent(addedNode);
this.setPreviousState();
} else {
this.treeControl.collapse(clickedNode);
}
}
findDataSource(item, node) {
if (item.RoleName === node.RoleName) {
return item;
} else if (item.CategoryPermissions) {
let matchedItem;
item.CategoryPermissions.forEach((e) => {
const temp = this.findDataSource(e, node);
if (temp) {
matchedItem = temp;
}
});
return matchedItem;
}
}
setPreviousState() {
for (let i = 0, j = 0; i < this.treeControl.dataNodes.length; i++) {
if (
this.tempNodes[j] &&
this.treeControl.dataNodes[i].RoleName === this.tempNodes[j].RoleName &&
this.treeControl.dataNodes[i].CatId === this.tempNodes[j].CatId &&
this.treeControl.dataNodes[i].level === this.tempNodes[j].level
) {
if (this.tempNodes[j].expanded) {
this.treeControl.expand(this.treeControl.dataNodes[i]);
}
j++;
}
}
}
expandParent(node: ExampleFlatNode) {
const { treeControl } = this;
const currentLevel = treeControl.getLevel(node);
const index = treeControl.dataNodes.indexOf(node) - 1;
for (let i = index; i >= 0; i--) {
const currentNode = treeControl.dataNodes[i];
if (currentLevel === 0) {
this.treeControl.expand(currentNode);
return null;
}
if (treeControl.getLevel(currentNode) < currentLevel) {
this.treeControl.expand(currentNode);
this.expandParent(currentNode);
break;
}
}
}

when I pushed the phone button to back previous page in Unity webviewScript

using System.Collections;
using UnityEngine;
using System;
using System.Collections.Generic;
public class SampleWebView : MonoBehaviour
{
public string Url;
public GUIText status;
WebViewObject webViewObject;
IEnumerator Start()
{
webViewObject = (new GameObject("WebViewObject")).AddComponent<WebViewObject>();
webViewObject.Init(
cb: (msg) =>
{
Debug.Log(string.Format("CallFromJS[{0}]", msg));
status.text = msg;
status.GetComponent<Animation>().Play();
},
err: (msg) =>
{
Debug.Log(string.Format("CallOnError[{0}]", msg));
status.text = msg;
status.GetComponent<Animation>().Play();
},
started: (msg) =>
{
Debug.Log(string.Format("CallOnStarted[{0}]", msg));
},
ld: (msg) =>
{
Debug.Log(string.Format("CallOnLoaded[{0}]", msg));
#if UNITY_EDITOR_OSX || !UNITY_ANDROID
// NOTE: depending on the situation, you might prefer
// the 'iframe' approach.
// cf. https://github.com/gree/unity-webview/issues/189
#if true
webViewObject.EvaluateJS(#"
if (window && window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.unityControl) {
window.Unity = {
call: function(msg) {
window.webkit.messageHandlers.unityControl.postMessage(msg);
}
}
} else {
window.Unity = {
call: function(msg) {
window.location = 'unity:' + msg;
}
}
}
");
#else
webViewObject.EvaluateJS(#"
if (window && window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.unityControl) {
window.Unity = {
call: function(msg) {
window.webkit.messageHandlers.unityControl.postMessage(msg);
}
}
} else {
window.Unity = {
call: function(msg) {
var iframe = document.createElement('IFRAME');
iframe.setAttribute('src', 'unity:' + msg);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
}
}
");
#endif
#endif
webViewObject.EvaluateJS(#"Unity.call('ua=' + navigator.userAgent)");
},
//ua: "custom user agent string",
enableWKWebView: true);
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
webViewObject.bitmapRefreshCycle = 1;
#endif
webViewObject.SetMargins(10, 140, 10, Screen.height / 360);
webViewObject.SetVisibility(true);
#if !UNITY_WEBPLAYER
if (Url.StartsWith("http")) {
webViewObject.LoadURL(Url.Replace(" ", "%20"));
} else {
var exts = new string[]{
".jpg",
".js",
".html" // should be last
};
foreach (var ext in exts) {
var url = Url.Replace(".html", ext);
var src = System.IO.Path.Combine(Application.streamingAssetsPath, url);
var dst = System.IO.Path.Combine(Application.persistentDataPath, url);
byte[] result = null;
if (src.Contains("://")) { // for Android
var www = new WWW(src);
yield return www;
result = www.bytes;
} else {
result = System.IO.File.ReadAllBytes(src);
}
System.IO.File.WriteAllBytes(dst, result);
if (ext == ".html") {
webViewObject.LoadURL("file://" + dst.Replace(" ", "%20"));
break;
}
}
}
#else
if (Url.StartsWith("http")) {
webViewObject.LoadURL(Url.Replace(" ", "%20"));
} else {
webViewObject.LoadURL("StreamingAssets/" + Url.Replace(" ", "%20"));
}
webViewObject.EvaluateJS(
"parent.$(function() {" +
" window.Unity = {" +
" call:function(msg) {" +
" parent.unityWebView.sendMessage('WebViewObject', msg)" +
" }" +
" };" +
"});");
#endif
yield break;
}
#if !UNITY_WEBPLAYER
//void OnGUI()
//{
// GUI.enabled = webViewObject.CanGoBack();
// if (GUI.Button(new Rect(10, 10, 80, 80), "<")) {
// webViewObject.GoBack();
// }
// GUI.enabled = true;
// GUI.enabled = webViewObject.CanGoForward();
// if (GUI.Button(new Rect(100, 10, 80, 80), ">")) {
// webViewObject.GoForward();
// }
// GUI.enabled = true;
// GUI.TextField(new Rect(200, 10, 300, 80), "" + webViewObject.Progress());
//}
#endif
This is my webview Scirpt for my newspaper App in C#. I need code, when I pushed the phone button to go back previous page in the newspaper.
I tried,
if(Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
However it quits from the app. It does not go back previous page in the newspaper..
Thank you for your help...
if i understand it correctly you should be good if you replace
if(Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
by this:
if(Input.GetKeyDown(KeyCode.Escape))
{
webViewObject.GoBack();
}
According to the example you are using from here https://github.com/gree/unity-webview/blob/master/sample/Assets/Scripts/SampleWebView.cs this is the build in back function. You can see it in the commented out part in your code.

Refresh sticky mobile leaderboard ad slot by changing the content URL in infinite scroll

I want to refresh sticky mobile leaderboard slot when the URL changes in infinite scroll. What do you think is the best way to go? Please let me know if you have any suggestions.
class DFPAds {
constructor() {
this.slots = [];
this.onScroll = throttle(this.loopAds, 300);
this.addEvents();
this.createAdObject = this.createAdObject.bind(this);
this.createSlotForAd = this.createSlotForAd.bind(this);
this.displayAd = this.displayAd.bind(this);
}
static get() {
return DFPAds._instance;
}
static set() {
if (!DFPAds._instance) {
DFPAds._instance = new DFPAds();
return DFPAds._instance;
} else {
throw new Error("DFPAds: instance already initialized");
}
}
addEvents() {
window.addEventListener("scroll", e => this.onScroll());
}
loopAds() {
this.slots.map(slot => this.displayAd(slot));
}
createAdObject(ad) {
let id = ad.id;
let attributes = getDataSet(ad);
let sizes = JSON.parse(attributes.sizes);
let sizeMapping;
if (attributes.sizemapping) {
attributes.sizemapping.length
? (sizeMapping = JSON.parse(attributes.sizemapping))
: (sizeMapping = null);
}
attributes.id = id;
attributes.sizes = sizes;
attributes.sizemapping = sizeMapping;
return attributes;
}
createSlotForAd(adObject) {
let {
id,
adtype,
position,
slotname,
sizes,
sizemapping,
shouldlazyload,
pagenumber,
pageid
} = adObject;
googletag.cmd.push(() => {
let slot = googletag.defineSlot(slotname, sizes, id);
if(position){
slot.setTargeting("position", position);
}
if(pagenumber){
slot.setTargeting("pagenumber", pagenumber);
}
if(pageid){
slot.setTargeting("PageID", pageid)
}
if (sizemapping) {
let mapping = googletag.sizeMapping();
sizemapping.map(size => {
mapping.addSize(size[0], size[1])
});
slot.defineSizeMapping(mapping.build());
}
slot.addService(googletag.pubads());
googletag.display(id);
shouldlazyload
? this.slots.push({ slot: slot, id: id })
: googletag.pubads().refresh([slot]);
console.log("SlotTop", slot)
});
}
displayAd(slot) {
console.log("Slottwo", slot)
let item = document.getElementById(slot.id);
if (item) {
let parent = item.parentElement;
let index = this.slots.indexOf(slot);
let parentDimensions = parent.getBoundingClientRect();
if (
(parentDimensions.top - 300) < window.innerHeight &&
parentDimensions.top + 150 > 0 &&
parent.offsetParent != null
) {
googletag.cmd.push(function() {
googletag.pubads().refresh([slot.slot]);
});
this.slots.splice(index, 1);
}
}
}
setUpAdSlots(context = document) {
let ads = [].slice.call(context.getElementsByClassName("Ad-data"));
if (ads.length) {
ads.map(ad => compose(this.createSlotForAd, this.createAdObject)(ad));
}
}
}
export default DFPAds;
And this is my Infinite Scroll code:
export default class InfiniteScroll {
constructor() {
this._end = document.getElementById('InfiniteScroll-End');
this._container = document.getElementById('InfiniteScroll-Container');
if(!this._end || !this._container)
return;
this._articles = { };
this._triggeredArticles = [];
this._currentArticle = '';
this._apiUrl = '';
this._count = 1;
this._timedOut = false;
this._loading = false;
this._ended = false;
this._viewedParameter = "&alreadyViewedContentIds=";
this._articleSelector = "InfiniteScroll-Article-";
this._articleClass = "Article-Container";
this.setStartData();
this.onScroll = throttle(this.eventScroll, 200);
this.addEvents();
}
addEvents(){
setTimeout(()=>{
window.addEventListener('scroll', (e) => this.onScroll() );
}, 2000);
}
addToStore(article){
this._articles["a" + article.id] = article;
}
addToTriggeredArticles(id){
this._triggeredArticles.push(id);
}
getRequestUrl(){
return this._apiUrl + this._viewedParameter + Object.keys(this._articles).map(key => this._articles[key].id).join();
}
getLastArticle(){
return this._articles[Object.keys(this._articles)[Object.keys(this._articles).length-1]];
}
setStartData() {
let dataset = getDataSet(this._container);
if(dataset.hasOwnProperty('apiurl')){
let article = Article.get();
if(article){
this._apiUrl = dataset.apiurl;
this._currentArticle = "a" + article.id;
this.addToStore(article);
}else{
throw(new Error('Infinite Scroll: Article not initialized.'));
}
}else{
throw(new Error('Infinite Scroll: Start object missing "apiurl" property.'));
}
}
eventScroll() {
if(this.isApproachingNext()){
this.requestNextArticle();
}
if(!this.isMainArticle(this._articles[this._currentArticle].node)){
this.updateCurrentArticle();
}
}
eventRequestSuccess(data){
this._loading = false;
if(data != ''){
if(data.hasOwnProperty('Id') && data.hasOwnProperty('Html') && data.hasOwnProperty('Url')){
this.incrementCount();
let node = document.createElement('div');
node.id = this._articleSelector + data.Id;
node.innerHTML = data.Html;
node.className = this._articleClass;
this._container.appendChild(node);
this.initArticleUpNext({
img: data.Img,
title: data.ArticleTitle,
category: data.Category,
target: node.id
});
this.addToStore(
new Article({
id: data.Id,
node: node,
url: data.Url,
title: data.Title,
count: this._count,
nielsenProps: {
section: data.NeilsenSection,
sega: data.NeilsenSegmentA,
segb: data.NeilsenSegmentB,
segc: data.NeilsenSegmentC
}
})
);
}else{
this._ended = true;
throw(new Error('Infinite Scroll: Response does not have an ID, Url and HTML property'));
}
}else{
this._ended = true;
throw(new Error('Infinite Scroll: No new article was received.'));
}
}
eventRequestError(response){
this._loading = false;
this._ended = true;
throw(new Error("Infinite Scroll: New article request failed."));
}
requestNextArticle(){
if(!this._loading){
this._loading = true;
return API.requestJSON(this.getRequestUrl())
.then(
(response)=>{this.eventRequestSuccess(response)},
(response)=>{this.eventRequestError(response)}
);
}
}
triggerViewEvent(article){
if(article.count > 1 && !this.isAlreadyTriggeredArticle(article.id)){
this.addToTriggeredArticles(article.id);
Tracker.pushView(article.url, article.count);
if(isFeatureEnabled('Nielsen') && Nielsen.get()){
Nielsen.get().eventInfiniteScroll({
id: article.id,
url: article.url,
section: article.nielsenProps.section,
sega: article.nielsenProps.sega,
segb: article.nielsenProps.segb,
segc: article.nielsenProps.segc
});
NielsenV60.trackEvent(article.title);
}
}
}
updateCurrentArticle(){
Object.keys(this._articles).map( key => {
if(this._currentArticle !== key && this.isMainArticle(this._articles[key].node)){
this._currentArticle = key;
this.updateUrl(this._articles[key]);
this.triggerViewEvent(this._articles[key]);
}
});
}
updateUrl(article){
try{
if(history.replaceState){
if(window.location.pathname !== article.url){
history.replaceState('', article.title, article.url);
}
}
}catch(e){}
}
incrementCount(){
this._count = this._count + 1;
}
initArticleUpNext(data){
this.getLastArticle().initUpNext(data);
}
isApproachingNext(){
return window.pageYOffset > this._end.offsetTop - (window.innerHeight * 2) && !this._ended && this._end.offsetTop >= 100;
}
isMainArticle(node){
if(node.getBoundingClientRect){
return (node.getBoundingClientRect().top < 80 && node.getBoundingClientRect().bottom > 70);
}else{
return false;
}
}
isAlreadyTriggeredArticle(id){
return this._triggeredArticles.indexOf(id) > -1;
}
}

TypeError: 'undefined' is not a function (evaluating 'ime.registIMEKey()')

Despite of setting and defining everything in Samsung smart TV SDK 4.0 I am getting this error:
TypeError: 'undefined' is not a function (evaluating 'ime.registIMEKey()')
Please help!
CODE:
var widgetAPI = new Common.API.Widget();
var tvKey = new Common.API.TVKeyValue();
var wapal_magic =
{
elementIds: new Array(),
inputs: new Array(),
ready: new Array()
};
/////////////////////////
var Input = function (id, previousId, nextId) {
var previousElement = document.getElementById(previousId),
nextElement = document.getElementById(nextId);
var installFocusKeyCallbacks = function () {
ime.setKeyFunc(tvKey.KEY_UP, function (keyCode) {
previousElement.focus();
return false;
});
ime.setKeyFunc(tvKey.KEY_DOWN, function (keyCode) {
nextElement.focus();
return false;
});
ime.setKeyFunc(tvKey.KEY_RETURN, function (keyCode) {
widgetAPI.blockNavigation();
return false;
});
ime.setKeyFunc(tvKey.KEY_EXIT, function (keyCode) {
widgetAPI.blockNavigation();
return false;
});
}
var imeReady = function (imeObject) {
installFocusKeyCallbacks();
wapal_magic.ready(id);
},
ime = new IMEShell(id, imeReady, 'en'),
element = document.getElementById(id);
}
wapal_magic.createInputObjects = function () {
var index,
previousIndex,
nextIndex;
for (index in this.elementIds) {
previousIndex = index - 1;
if (previousIndex < 0) {
previousIndex = wapal_magic.inputs.length - 1;
}
nextIndex = (index + 1) % wapal_magic.inputs.length;
wapal_magic.inputs[index] = new Input(this.elementIds[index],
this.elementIds[previousIndex], this.elementIds[nextIndex]);
}
};
wapal_magic.ready = function (id) {
var ready = true,
i;
for (i in wapal_magic.elementIds) {
if (wapal_magic.elementIds[i] == id) {
wapal_magic.ready[i] = true;
}
if (wapal_magic.ready[i] == false) {
ready = false;
}
}
if (ready) {
document.getElementById("txtInp1").focus();
}
};
////////////////////////
wapal_magic.onLoad = function()
{
// Enable key event processing
//this.enableKeys();
// widgetAPI.sendReadyEvent();
this.initTextBoxes(new Array("txtInp1", "txtInp2"));
};
wapal_magic.initTextBoxes = function(textboxes){
this.elementIds = textboxes;
for(i=0;i<this.elementIds.length;i++){
this.inputs[i]=false;
this.ready[i]=null;
}
this.createInputObjects();
widgetAPI.registIMEKey();
};
wapal_magic.onUnload = function()
{
};
wapal_magic.enableKeys = function()
{
document.getElementById("anchor").focus();
};
wapal_magic.keyDown = function()
{
var keyCode = event.keyCode;
alert("Key pressed: " + keyCode);
switch(keyCode)
{
case tvKey.KEY_RETURN:
case tvKey.KEY_PANEL_RETURN:
alert("RETURN");
widgetAPI.sendReturnEvent();
break;
case tvKey.KEY_LEFT:
alert("LEFT");
break;
case tvKey.KEY_RIGHT:
alert("RIGHT");
break;
case tvKey.KEY_UP:
alert("UP");
break;
case tvKey.KEY_DOWN:
alert("DOWN");
break;
case tvKey.KEY_ENTER:
case tvKey.KEY_PANEL_ENTER:
alert("ENTER");
break;
default:
alert("Unhandled key");
break;
}
};
The registIMEKey method is part of the Plugin API.
var pluginAPI = new Common.API.Plugin();
pluginAPI.registIMEKey();
See: http://www.samsungdforum.com/Guide/ref00006/common_module_plugin_object.html#ref00006-common-module-plugin-object-registimekey
Edit: Updated to add code solution.
widgetAPI no contains method registIMEKey();, it contains in IMEShell.