Advertisement
  1. Code
  2. PhoneGap

Bangun AudioPlayer dengan PhoneGap: Logika Aplikasi

Scroll to top
Read Time: 13 min

() translation by (you can also view the original English article)

Ini adalah bagian kedua dari seri tentang Audero Audio Player. Dalam artikel ini, kita akan membuat logika bisnis pemain kita. Saya juga akan menjelaskan beberapa API Cordova yang diperkenalkan di artikel sebelumnya.


Ikhtisar Seri


Menciptakan Player

Di bagian ini, saya akan menunjukkan kepada Anda kelas yang disebut Player, yang memungkinkan kami bermain, berhenti, mundur, dan maju cepat. Kelas sangat bergantung pada API Media; tanpa metode, pemain kami akan benar-benar tidak berguna. Selain API Media, kelas ini mengambil keuntungan dari metode alert() pada Pemberitahuan API. Tampilan lansiran bervariasi di antara platform. Sebagian besar sistem operasi yang didukung menggunakan kotak dialog asli tetapi yang lain, seperti Bada 2.X, menggunakan fungsi alert () klasik browser, yang kurang dapat disesuaikan. Metode sebelumnya menerima hingga empat parameter:

  1. message: String yang berisi pesan untuk ditampilkan
  2. alertCallback: Callback untuk memanggil ketika dialog peringatan ditutup
  3. title: Judul dialog (nilai defaultnya adalah "Alert")
  4. buttonName: Teks tombol yang termasuk dalam dialog (nilai default adalah "OK")

Ingatlah bahwa Windows Phone 7 mengabaikan nama tombol dan selalu menggunakan default. Windows Phone 7 dan 8 tidak memiliki peringatan browser built-in, jadi jika Anda ingin menggunakan alert ('message') ;, Anda harus menetapkan window.alert = navigator.notification.alert.

Sekarang setelah saya menjelaskan API yang digunakan oleh Player, kita dapat melihat cara pembuatannya. Kami memiliki tiga properti:

  • media: referensi ke objek suara saat ini
  • mediaTimer: yang akan berisi ID interval unik yang dibuat menggunakan fungsi setInterval () yang akan kita lewati ke clearInterval () untuk menghentikan pengatur suara
  • isPlaying: variabel yang menentukan apakah suara saat ini diputar atau tidak. Selain properti, kelas memiliki beberapa metode.

Metode initMedia () menginisialisasi properti media dengan objek Media yang mewakili suara yang dipilih oleh pengguna. Yang terakhir ini diberitahukan menggunakan API Pemberitahuan jika terjadi kesalahan. Tujuan dari metode playPause, stop (), dan seekPosition () harus jelas, jadi saya akan melanjutkan. Metode resetLayout () dan changePlayButton () sangat sederhana. Mereka digunakan untuk mengatur ulang atau memperbarui tata letak pemain sesuai dengan tindakan yang dilakukan oleh pengguna. Metode terakhir yang tersisa adalah updateSliderPosition (), yang mirip dengan penggeser waktu. Yang terakhir memiliki nol (awal slider) sebagai nilai default untuk posisi saat ini, atur menggunakan atribut value = "0". Ini harus diperbarui secara bersamaan saat suara dimainkan untuk diberikan kepada pengguna umpan balik visual mengenai waktu pemutaran yang telah berlalu.

Kami telah menemukan semua detail kelas ini, jadi di sini adalah kode sumber file:

1
2
var Player = {
3
   media: null,
4
   mediaTimer: null,
5
   isPlaying: false,
6
   initMedia: function(path) {
7
      Player.media = new Media(
8
         path,
9
         function() {
10
            console.log('Media file read succesfully');
11
            if (Player.media !== null)
12
               Player.media.release();
13
            Player.resetLayout();
14
         },
15
         function(error) {
16
            navigator.notification.alert(
17
               'Unable to read the media file.',
18
               function(){},
19
               'Error'
20
            );
21
            Player.changePlayButton('play');
22
            console.log('Unable to read the media file (Code): ' + error.code);
23
         }
24
      );
25
   },
26
   playPause: function(path) {
27
      if (Player.media === null)
28
         Player.initMedia(path);
29
30
      if (Player.isPlaying === false)
31
      {
32
         Player.media.play();
33
         Player.mediaTimer = setInterval(
34
            function() {
35
               Player.media.getCurrentPosition(
36
                  function(position) {
37
                     if (position > -1)
38
                     {
39
                        $('#media-played').text(Utility.formatTime(position));
40
                        Player.updateSliderPosition(position);
41
                     }
42
                  },
43
                  function(error) {
44
                     console.log('Unable to retrieve media position: ' + error.code);
45
                     $('#media-played').text(Utility.formatTime(0));
46
                  }
47
               );
48
            },
49
            1000
50
         );
51
         var counter = 0;
52
         var timerDuration = setInterval(
53
            function() {
54
               counter++;
55
               if (counter > 20)
56
                  clearInterval(timerDuration);
57
58
               var duration = Player.media.getDuration();
59
               if (duration > -1)
60
               {
61
                  clearInterval(timerDuration);
62
                  $('#media-duration').text(Utility.formatTime(duration));
63
                  $('#time-slider').attr('max', Math.round(duration));
64
                  $('#time-slider').slider('refresh');
65
               }
66
               else
67
                  $('#media-duration').text('Unknown');
68
            },
69
            100
70
         );
71
72
         Player.changePlayButton('pause');
73
      }
74
      else
75
      {
76
         Player.media.pause();
77
         clearInterval(Player.mediaTimer);
78
         Player.changePlayButton('play');
79
      }
80
      Player.isPlaying = !Player.isPlaying;
81
   },
82
   stop: function() {
83
      if (Player.media !== null)
84
      {
85
         Player.media.stop();
86
         Player.media.release();
87
      }
88
      clearInterval(Player.mediaTimer);
89
      Player.media = null;
90
      Player.isPlaying = false;
91
      Player.resetLayout();
92
   },
93
   resetLayout: function() {
94
      $('#media-played').text(Utility.formatTime(0));
95
      Player.changePlayButton('play');
96
      Player.updateSliderPosition(0);
97
   },
98
   updateSliderPosition: function(seconds) {
99
      var $slider = $('#time-slider');
100
101
      if (seconds < $slider.attr('min'))
102
         $slider.val($slider.attr('min'));
103
      else if (seconds > $slider.attr('max'))
104
         $slider.val($slider.attr('max'));
105
      else
106
         $slider.val(Math.round(seconds));
107
108
      $slider.slider('refresh');
109
   },
110
   seekPosition: function(seconds) {
111
      if (Player.media === null)
112
         return;
113
114
      Player.media.seekTo(seconds * 1000);
115
      Player.updateSliderPosition(seconds);
116
   },
117
   changePlayButton: function(imageName) {
118
      var background = $('#player-play')
119
      .css('background-image')
120
      .replace('url(', '')
121
      .replace(')', '');
122
123
      $('#player-play').css(
124
         'background-image',
125
         'url(' + background.replace(/images\/.*\.png$/, 'images/' + imageName + '.png') + ')'
126
      );
127
   }
128
};

Mengelola File Audio

Bagian ini mengilustrasikan kelas AppFile yang akan digunakan untuk membuat, menghapus, dan memuat suara menggunakan Web Storage API. API ini memiliki dua area, Sesi dan Lokal, tetapi Cordova menggunakan yang terakhir. Semua suara disimpan dalam item yang berjudul "file" seperti yang Anda lihat dengan melihat properti _tableName.

Harap perhatikan bahwa API ini hanya dapat menyimpan data dasar. Oleh karena itu, agar sesuai dengan kebutuhan kita untuk menyimpan objek, kita akan menggunakan format JSON. JavaScript memiliki kelas untuk menangani format yang disebut JSON ini. Ia menggunakan metode parse() untuk mem-parse string dan membuat ulang data yang sesuai, dan stringify() untuk mengonversi objek dalam string. Sebagai catatan akhir, saya tidak akan menggunakan notasi titik dari API karena Windows Phone 7 tidak mendukungnya, jadi kami akan menggunakan metode setItem() dan getItem () untuk memastikan kompatibilitas untuk semua perangkat.

Sekarang setelah Anda memiliki gambaran tentang bagaimana kami akan menyimpan data, mari kita bicara tentang data yang perlu kita simpan. Satu-satunya informasi yang kita butuhkan untuk setiap suara yang ditemukan adalah nama (name properti) dan jalur absolut (properti fullPath). Kelas AppFile juga memiliki "konstan", disebut EXTENSIONS, di mana kami akan mengatur ekstensi yang akan diuji terhadap setiap file. Jika mereka cocok, file akan dikumpulkan oleh aplikasi. Kami memiliki metode untuk menambahkan file (addFile()), salah satu metode untuk menghapus file (deleteFile()), salah satu metode yang menghapus seluruh database (deleteFiles()), dan, terakhir, dua metode yang mengambil file dari database: getAppFiles() untuk mengambil semua file, dan getAppFile() untuk mengambil satu saja. Kelas ini juga memiliki empat metode perbandingan, dua statis (compare() dan compareIgnoreCase()) dan dua non-statis (compareTo() dan compareToIgnoreCase()). Metode terakhir adalah yang digunakan untuk mengambil indeks file tertentu, getIndex(). Kelas AppFile memungkinkan Anda untuk melakukan semua operasi dasar yang mungkin Anda butuhkan.

Kode yang mengimplementasikan apa yang telah kita bahas dapat dibaca di sini:

1
2
function AppFile(name, fullPath)
3
{
4
   var _db = window.localStorage;
5
   var _tableName = 'files';
6
7
   this.name = name;
8
   this.fullPath = fullPath;
9
10
   this.save = function(files)
11
   {
12
      _db.setItem(_tableName, JSON.stringify(files));
13
   }
14
15
   this.load = function()
16
   {
17
      return JSON.parse(_db.getItem(_tableName));
18
   }
19
}
20
21
AppFile.prototype.addFile = function()
22
{
23
   var index = AppFile.getIndex(this.fullPath);
24
   var files = AppFile.getAppFiles();
25
26
   if (index === false)
27
      files.push(this);
28
   else
29
      files[index] = this;
30
31
   this.save(files);
32
};
33
34
AppFile.prototype.deleteFile = function()
35
{
36
   var index = AppFile.getIndex(this.fullPath);
37
   var files = AppFile.getAppFiles();
38
   if (index !== false)
39
   {
40
      files.splice(index, 1);
41
      this.save(files);
42
   }
43
44
   return files;
45
};
46
47
AppFile.prototype.compareTo = function(other)
48
{
49
   return AppFile.compare(this, other);
50
};
51
52
AppFile.prototype.compareToIgnoreCase = function(other)
53
{
54
   return AppFile.compareIgnoreCase(this, other);
55
};
56
57
AppFile.EXTENSIONS = ['.mp3', '.wav', '.m4a'];
58
59
AppFile.compare = function(appFile, other)
60
{
61
   if (other == null)
62
      return 1;
63
   else if (appFile == null)
64
      return -1;
65
66
   return appFile.name.localeCompare(other.name);
67
};
68
69
AppFile.compareIgnoreCase = function(appFile, other)
70
{
71
   if (other == null)
72
      return 1;
73
   else if (appFile == null)
74
      return -1;
75
76
   return appFile.name.toUpperCase().localeCompare(other.name.toUpperCase());
77
};
78
79
AppFile.getAppFiles = function()
80
{
81
   var files = new AppFile().load();
82
   return (files === null) ? [] : files;
83
};
84
85
AppFile.getAppFile = function(path)
86
{
87
   var index = AppFile.getIndex(path);
88
   if (index === false)
89
      return null;
90
   else
91
   {
92
      var file = AppFile.getAppFiles()[index];
93
      return new AppFile(file.name, file.fullPath);
94
   }
95
};
96
97
AppFile.getIndex = function(path)
98
{
99
   var files = AppFile.getAppFiles();
100
   for(var i = 0; i < files.length; i++)
101
   {
102
      if (files[i].fullPath.toUpperCase() === path.toUpperCase())
103
         return i;
104
   }
105
106
   return false;
107
};
108
109
AppFile.deleteFiles = function()
110
{
111
   new AppFile().save([]);
112
};

Kelas Utilitas

File utility.js sangat singkat dan mudah dimengerti. Hanya memiliki dua metode. Satu digunakan untuk mengonversi milidetik menjadi string berformat yang akan ditampilkan di pemutar, sementara yang lain adalah implementasi JavaScript dari metode Java yang terkenal endsWith.

Inilah sumbernya:

1
2
var Utility = {
3
   formatTime: function(milliseconds) {
4
      if (milliseconds <= 0)
5
         return '00:00';
6
7
      var seconds = Math.round(milliseconds);
8
      var minutes = Math.floor(seconds / 60);
9
      if (minutes < 10)
10
         minutes = '0' + minutes;
11
12
      seconds = seconds % 60;
13
      if (seconds < 10)
14
         seconds = '0' + seconds;
15
16
      return minutes + ':' + seconds;
17
   },
18
   endsWith: function(string, suffix) {
19
      return string.indexOf(suffix, string.length - suffix.length) !== -1;
20
   }
21
};

Menempatkannya Semua Bersama

Bagian ini membahas file JavaScript terakhir dari proyek, application.js, yang berisi kelas Aplikasi. Tujuannya adalah melampirkan acara ke elemen halaman aplikasi. Kejadian-kejadian itu akan memanfaatkan kelas yang telah kita lihat sejauh ini dan memungkinkan pemain untuk bekerja dengan benar.

Kode fungsi yang diilustrasikan tercantum di bawah ini:

1
2
var Application = {
3
   initApplication: function() {
4
      $(document).on(
5
         'pageinit',
6
         '#files-list-page',
7
         function()
8
         {
9
            Application.initFilesListPage();
10
         }
11
      );
12
      $(document).on(
13
         'pageinit',
14
         '#aurelio-page',
15
         function()
16
         {
17
            Application.openLinksInApp();
18
         }
19
      );
20
      $(document).on(
21
         'pagechange',
22
         function(event, properties)
23
         {
24
            if (properties.absUrl === $.mobile.path.makeUrlAbsolute('player.html'))
25
            {
26
               Application.initPlayerPage(
27
                  JSON.parse(properties.options.data.file)
28
               );
29
            }
30
         }
31
      );
32
   },
33
   initFilesListPage: function() {
34
      $('#update-button').click(
35
         function()
36
         {
37
            $('#waiting-popup').popup('open');
38
            setTimeout(function(){
39
               Application.updateMediaList();
40
            }, 150);
41
         }
42
      );
43
      $(document).on('endupdate', function(){
44
         Application.createFilesList('files-list', AppFile.getAppFiles());
45
         $('#waiting-popup').popup('close');
46
      });
47
      Application.createFilesList('files-list', AppFile.getAppFiles());
48
   },
49
   initPlayerPage: function(file) {
50
      Player.stop();
51
      $('#media-name').text(file.name);
52
      $('#media-path').text(file.fullPath);
53
      $('#player-play').click(function() {
54
         Player.playPause(file.fullPath);
55
      });
56
      $('#player-stop').click(Player.stop);
57
      $('#time-slider').on('slidestop', function(event) {
58
         Player.seekPosition(event.target.value);
59
      });
60
   },
61
   updateIcons: function()
62
   {
63
      if ($(window).width() > 480)
64
      {
65
         $('a[data-icon], button[data-icon]').each(function() {
66
            $(this).removeAttr('data-iconpos');
67
         });
68
      }
69
      else
70
      {
71
         $('a[data-icon], button[data-icon]').each(function() {
72
            $(this).attr('data-iconpos', 'notext');
73
         });
74
      }
75
   },
76
   openLinksInApp: function()
77
   {
78
      $("a[target=\"_blank\"]").on('click', function(event) {
79
         event.preventDefault();
80
         window.open($(this).attr('href'), '_target');
81
      });
82
   },
83
   updateMediaList: function() {
84
      window.requestFileSystem(
85
         LocalFileSystem.PERSISTENT,
86
         0,
87
         function(fileSystem){
88
            var root = fileSystem.root;
89
            AppFile.deleteFiles();
90
            Application.collectMedia(root.fullPath, true);
91
         },
92
         function(error){
93
            console.log('File System Error: ' + error.code);
94
         }
95
      );
96
   },
97
   collectMedia: function(path, recursive, level) {
98
      if (level === undefined)
99
         level = 0;
100
      var directoryEntry = new DirectoryEntry('', path);
101
      if(!directoryEntry.isDirectory) {
102
         console.log('The provided path is not a directory');
103
         return;
104
      }
105
      var directoryReader = directoryEntry.createReader();
106
      directoryReader.readEntries(
107
         function (entries) {
108
            var appFile;
109
            var extension;
110
            for (var i = 0; i < entries.length; i++) {
111
               if (entries[i].name === '.')
112
                  continue;
113
114
               extension = entries[i].name.substr(entries[i].name.lastIndexOf('.'));
115
               if (entries[i].isDirectory === true && recursive === true)
116
                  Application.collectMedia(entries[i].fullPath, recursive, level + 1);
117
               else if (entries[i].isFile === true && $.inArray(extension, AppFile.EXTENSIONS) >= 0)
118
               {
119
                  appFile = new AppFile(entries[i].name, entries[i].fullPath);
120
                  appFile.addFile();
121
                  console.log('File saved: ' + entries[i].fullPath);
122
               }
123
            }
124
         },
125
         function(error) {
126
            console.log('Unable to read the directory. Errore: ' + error.code);
127
         }
128
      );
129
130
      if (level === 0)
131
         $(document).trigger('endupdate');
132
      console.log('Current path analized is: ' + path);
133
   },
134
   createFilesList: function(idElement, files)
135
   {
136
      $('#' + idElement).empty();
137
138
      if (files == null || files.length == 0)
139
      {
140
         $('#' + idElement).append('<p>No files to show. Would you consider a files update (top right button)?</p>');
141
         return;
142
      }
143
144
      function getPlayHandler(file) {
145
         return function playHandler() {
146
            $.mobile.changePage(
147
               'player.html',
148
               {
149
                  data: {
150
                     file: JSON.stringify(file)
151
                  }
152
               }
153
            );
154
         };
155
      }
156
157
      function getDeleteHandler(file) {
158
         return function deleteHandler() {
159
            var oldLenght = AppFile.getAppFiles().length;
160
            var $parentUl = $(this).closest('ul');
161
162
            file = new AppFile('', file.fullPath);
163
            file.deleteFile();
164
            if (oldLenght === AppFile.getAppFiles().length + 1)
165
            {
166
               $(this).closest('li').remove();
167
               $parentUl.listview('refresh');
168
            }
169
            else
170
            {
171
               console.log('Media not deleted. Something gone wrong.');
172
               navigator.notification.alert(
173
                  'Media not deleted. Something gone wrong so please try again.',
174
                  function(){},
175
                  'Error'
176
               );
177
            }
178
         };
179
      }
180
181
      var $listElement, $linkElement;
182
      files.sort(AppFile.compareIgnoreCase);
183
      for(var i = 0; i < files.length; i++)
184
      {
185
         $listElement = $('<li>');
186
         $linkElement = $('<a>');
187
         $linkElement
188
         .attr('href', '#')
189
         .text(files[i].name)
190
         .click(getPlayHandler(files[i]));
191
192
         // Append the link to the <li> element

193
         $listElement.append($linkElement);
194
195
         $linkElement = $('<a>');
196
         $linkElement
197
         .attr('href', '#')
198
         .text('Delete')
199
         .click(getDeleteHandler(files[i]));
200
201
         // Append the link to the <li> element

202
         $listElement.append($linkElement);
203
204
         // Append the <li> element to the <ul> element

205
         $('#' + idElement).append($listElement);
206
      }
207
      $('#' + idElement).listview('refresh');
208
   }
209
};

Mengelola Tautan Eksternal

Di bagian sebelumnya dari seri ini, saya menyebutkan bahwa poin menarik dari halaman kredit adalah atribut target="_blank" yang diterapkan pada tautan. Bagian ini akan menjelaskan mengapa metode openLinksInApp () kelas Aplikasi masuk akal.

Sekali waktu, Cordova digunakan untuk membuka tautan eksternal di Cordova WebView yang sama yang menjalankan aplikasi. Saat ini, tautan eksternal dibuka, secara default, menggunakan Cordova WebView jika URL ada di daftar putih aplikasi Anda. URL yang tidak ada dalam daftar putih Anda dibuka menggunakan API InAppBrowser. Saat ini, tautan eksternal dibuka, secara default, menggunakan Cordova WebView jika URL ada di daftar putih aplikasi Anda. URL yang tidak ada dalam daftar putih Anda dibuka menggunakan API InAppBrowser. Jika Anda tidak mengelola tautan dengan cara yang benar, atau jika pengguna mengetuk tautan yang ditampilkan di InAppBrowser atau sistem dan kemudian memilih untuk kembali, semua perangkat seluler jQuery Mobile hilang. Perilaku ini terjadi karena file CSS dan JavaScript dimuat oleh halaman utama, dan yang berikut dimuat menggunakan AJAX. Sebelum mengungkap solusi, mari kita lihat apa yang InAppBrowser.

The InAppBrowser adalah browser web yang ditampilkan di aplikasi Anda ketika Anda menggunakan jendela. Buka panggilan.

API ini memiliki tiga metode:

  • addEventListener (): Memungkinkan Anda untuk mendengarkan tiga peristiwa (loadstart, loadstop, dan exit) dan melampirkan fungsi yang berjalan segera setelah peristiwa-peristiwa itu dipecat
  • removeEventListener (): Menghapus listener yang terlampir sebelumnya.
  • close (): Digunakan untuk menutup jendela InAppBrowser.

Jadi, apa solusinya? Tujuan dari fungsi openLinksInApp (), digabungkan dengan whitelist yang ditentukan dalam file konfigurasi, adalah untuk menangkap klik pada semua tautan eksternal yang dikenali dengan menggunakan atribut target = "_ blank", dan membukanya menggunakan window.open () metode. Dengan teknik ini, kami akan menghindari masalah yang dijelaskan, dan pemain kami akan terus melihat dan bekerja seperti yang diharapkan.


Bagian Selanjutnya

Pada seri ketiga dan terakhir dari seri ini, kita akan melihat file terakhir yang tersisa sehingga Anda dapat menyelesaikan proyek dan bermain-main dengannya.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.