start on device list, don't crash when list is empty
[enigma2.git] / lib / python / Screens / MediaPlayer.py
1 from enigma import eTimer, iPlayableService, eServiceCenter, iServiceInformation, eSize
2 from Screens.Screen import Screen
3 from Screens.MessageBox import MessageBox
4 from Components.ActionMap import NumberActionMap
5 from Components.Label import Label
6 from Components.Input import Input
7 from Components.GUIComponent import *
8 from Components.Pixmap import Pixmap
9 from Components.Label import Label
10 from Components.FileList import FileEntryComponent, FileList
11 from Components.MediaPlayer import PlayList, PlaylistEntryComponent
12 from Plugins.Plugin import PluginDescriptor
13 from Tools.Directories import resolveFilename, SCOPE_MEDIA, SCOPE_CONFIG, SCOPE_SKIN_IMAGE
14 from Components.ServicePosition import ServicePositionGauge
15 from Screens.ChoiceBox import ChoiceBox
16 from Components.ServiceEventTracker import ServiceEventTracker
17 from Components.Playlist import PlaylistIOInternal, PlaylistIOM3U, PlaylistIOPLS
18 from Screens.InfoBarGenerics import InfoBarSeek
19 from ServiceReference import ServiceReference
20 from Screens.ChoiceBox import ChoiceBox
21
22 import os
23
24 class MediaPlayer(Screen, InfoBarSeek):
25         ALLOW_SUSPEND = True
26
27         def __init__(self, session, args = None):
28                 Screen.__init__(self, session)
29                 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
30                 self.session.nav.stopService()
31
32                 self.playlistparsers = {}
33                 self.addPlaylistParser(PlaylistIOM3U, "m3u")
34                 self.addPlaylistParser(PlaylistIOPLS, "pls")
35                 self.addPlaylistParser(PlaylistIOInternal, "e2pls")
36
37                 # 'None' is magic to start at the list of mountpoints
38                 self.filelist = FileList(None, matchingPattern = "(?i)^.*\.(mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob)", useServiceRef = True)
39                 self["filelist"] = self.filelist
40
41                 self.playlist = PlayList()
42                 self["playlist"] = self.playlist
43
44                 self["PositionGauge"] = ServicePositionGauge(self.session.nav)
45                 
46                 self["currenttext"] = Label("")
47
48                 self["artisttext"] = Label(_("Artist:"))
49                 self["artist"] = Label("")
50                 self["titletext"] = Label(_("Title:"))
51                 self["title"] = Label("")
52                 self["albumtext"] = Label(_("Album:"))
53                 self["album"] = Label("")
54                 self["yeartext"] = Label(_("Year:"))
55                 self["year"] = Label("")
56                 self["genretext"] = Label(_("Genre:"))
57                 self["genre"] = Label("")
58                 self["coverArt"] = Pixmap()
59                 
60                 self.seek_target = None
61                 
62                 #self["text"] = Input("1234", maxSize=True, type=Input.NUMBER)
63
64                 class MoviePlayerActionMap(NumberActionMap):
65                         def __init__(self, player, contexts = [ ], actions = { }, prio=0):
66                                 NumberActionMap.__init__(self, contexts, actions, prio)
67                                 self.player = player
68
69                         def action(self, contexts, action):
70                                 self.player.show()
71                                 return NumberActionMap.action(self, contexts, action)
72
73                 self["actions"] = MoviePlayerActionMap(self, ["OkCancelActions", "DirectionActions", "NumberActions", "MediaPlayerSeekActions"],
74                 {
75                         "ok": self.ok,
76                         "cancel": self.exit,
77
78                         "right": self.rightDown,
79                         "rightRepeated": self.doNothing,
80                         "rightUp": self.rightUp,
81                         "left": self.leftDown,
82                         "leftRepeated": self.doNothing,
83                         "leftUp": self.leftUp,
84
85                         "up": self.up,
86                         "upRepeated": self.up,
87                         "down": self.down,
88                         "downRepeated": self.down,
89
90                         "play": self.playEntry,
91                         "pause": self.pauseEntry,
92                         "stop": self.stopEntry,
93
94                         "previous": self.previousEntry,
95                         "next": self.nextEntry,
96
97                         "menu": self.showMenu,
98
99                         "1": self.keyNumberGlobal,
100                         "2": self.keyNumberGlobal,
101                         "3": self.keyNumberGlobal,
102                         "4": self.keyNumberGlobal,
103                         "5": self.keyNumberGlobal,
104                         "6": self.keyNumberGlobal,
105                         "7": self.keyNumberGlobal,
106                         "8": self.keyNumberGlobal,
107                         "9": self.keyNumberGlobal,
108                         "0": self.keyNumberGlobal
109                 }, -2)
110
111                 InfoBarSeek.__init__(self)
112
113                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
114                         {
115                                 #iPlayableService.evStart: self.__serviceStarted,
116                                 #iPlayableService.evSeekableStatusChanged: InfoBarSeek.__seekableStatusChanged,
117
118                                 iPlayableService.evEOF: self.__evEOF,
119 #                               iPlayableService.evSOF: self.__evSOF,
120                         })
121
122                 self.onClose.append(self.delMPTimer)
123                 self.onClose.append(self.__onClose)
124
125                 self.righttimer = False
126                 self.rightKeyTimer = eTimer()
127                 self.rightKeyTimer.timeout.get().append(self.rightTimerFire)
128
129                 self.lefttimer = False
130                 self.leftKeyTimer = eTimer()
131                 self.leftKeyTimer.timeout.get().append(self.leftTimerFire)
132
133                 self.infoTimer = eTimer()
134                 self.infoTimer.timeout.get().append(self.infoTimerFire)
135                 self.infoTimer.start(500)
136
137                 self.currList = "filelist"
138
139                 self.coverArtFileName = ""
140
141                 self.playlistIOInternal = PlaylistIOInternal()
142                 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
143                 if list:
144                         for x in list:
145                                 self.playlist.addFile(x.ref)
146                         self.playlist.updateList()
147
148         def doNothing(self):
149                 pass
150
151         def exit(self):
152                 self.playlistIOInternal.clear()
153                 for x in self.playlist.list:
154                         self.playlistIOInternal.addService(ServiceReference(x[0]))
155                 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
156                 self.close()
157
158         def checkSkipShowHideLock(self):
159                 self.updatedSeekState()
160
161         def __evEOF(self):
162                 self.nextEntry()
163
164         def __onClose(self):
165                 self.session.nav.playService(self.oldService)
166
167         def delMPTimer(self):
168                 del self.rightKeyTimer
169                 del self.leftKeyTimer
170                 del self.infoTimer
171
172         def infoTimerFire(self):
173                 currPlay = self.session.nav.getCurrentService()
174                 if currPlay is not None:
175                         self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
176                                                                                  title = currPlay.info().getInfoString(iServiceInformation.sTitle),
177                                                                                  album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
178                                                                                  genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
179                                                                                  clear = True)
180                         self.updateCoverArtPixmap( currPlay.info().getName() )
181                 else:
182                         self.updateMusicInformation()
183                         self.updateCoverArtPixmap( "" )
184
185         def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
186                 self.updateSingleMusicInformation("artist", artist, clear)
187                 self.updateSingleMusicInformation("title", title, clear)
188                 self.updateSingleMusicInformation("album", album, clear)
189                 self.updateSingleMusicInformation("year", year, clear)
190                 self.updateSingleMusicInformation("genre", genre, clear)
191
192         def updateSingleMusicInformation(self, name, info, clear):
193                 if info != "" or clear:
194                         if self[name].getText() != info:
195                                 self[name].setText(info)
196
197         def updateCoverArtPixmap(self, currentServiceName):
198                 filename = currentServiceName
199                 # The "getName" usually adds something like "MP3 File:" infront of filename
200                 # Get rid of this...by finding the first "/"
201                 filename = filename[filename.find("/"):]
202                 path = os.path.dirname(filename)
203                 pngname = path + "/" + "folder.png"
204                 if not os.path.exists(pngname):
205                         pngname = resolveFilename(SCOPE_SKIN_IMAGE, "no_coverArt.png")
206                 if self.coverArtFileName != pngname:
207                         self.coverArtFileName = pngname
208                         self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
209
210         def fwdTimerFire(self):
211                 self.fwdKeyTimer.stop()
212                 self.fwdtimer = False
213                 self.nextEntry()
214
215         def rwdTimerFire(self):
216                 self.rwdKeyTimer.stop()
217                 self.rwdtimer = False
218                 self.previousEntry()
219
220         def leftDown(self):
221                 self.lefttimer = True
222                 self.leftKeyTimer.start(1000)
223
224         def rightDown(self):
225                 self.righttimer = True
226                 self.rightKeyTimer.start(1000)
227
228         def leftUp(self):
229                 if self.lefttimer:
230                         self.leftKeyTimer.stop()
231                         self.lefttimer = False
232                         self[self.currList].pageUp()
233
234         def rightUp(self):
235                 if self.righttimer:
236                         self.rightKeyTimer.stop()
237                         self.righttimer = False
238                         self[self.currList].pageDown()
239
240         def leftTimerFire(self):
241                 self.leftKeyTimer.stop()
242                 self.lefttimer = False
243                 self.switchToFileList()
244
245         def rightTimerFire(self):
246                 self.rightKeyTimer.stop()
247                 self.righttimer = False
248                 self.switchToPlayList()
249
250         def switchToFileList(self):
251                 self.currList = "filelist"
252                 self.filelist.selectionEnabled(1)
253                 self.playlist.selectionEnabled(0)
254                 self.updateCurrentInfo()
255
256         def switchToPlayList(self):
257                 if len(self.playlist) != 0:
258                         self.currList = "playlist"
259                         self.filelist.selectionEnabled(0)
260                         self.playlist.selectionEnabled(1)
261                         self.updateCurrentInfo()
262
263         def up(self):
264                 self[self.currList].up()
265                 self.updateCurrentInfo()
266
267         def down(self):
268                 self[self.currList].down()
269                 self.updateCurrentInfo()
270
271         def updateCurrentInfo(self):
272                 text = ""
273                 if self.currList == "filelist":
274                         if not self.filelist.canDescent():
275                                 r = self.filelist.getServiceRef()
276                                 if r is None:
277                                         return
278                                 text = r.getPath()
279                 if self.currList == "playlist":
280                         t = self.playlist.getSelection()
281                         if t is None:
282                                 return
283                         text = t.getPath()
284
285                 self["currenttext"].setText(os.path.basename(text))
286
287         def ok(self):
288                 if self.currList == "filelist":
289                         if self.filelist.canDescent():
290                                 self.filelist.descent()
291                                 self.updateCurrentInfo()
292                         else:
293                                 self.stopEntry()
294                                 self.playlist.clear()
295                                 self.copyDirectory(os.path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
296                                 self.playServiceRefEntry(self.filelist.getServiceRef())
297                                 
298                 if self.currList == "playlist":
299                         selection = self["playlist"].getSelection()
300                         self.changeEntry(self.playlist.getSelectionIndex())
301
302         def keyNumberGlobal(self, number):
303                 if number == 5: # enable seeking
304                         if self.seek_target is None:
305                                 (len, pos) = self["PositionGauge"].get()
306                                 
307                                 if self.isSeekable() and len != 0:
308                                         self.seek_target = pos
309                         else:
310                                 self.seekAbsolute(self.seek_target)
311                                 self.seek_target = None
312                 elif number == 2: # abort
313                         self.seek_target = None
314                 elif (number == 4 or number == 6) and self.seek_target is not None:
315                         (len, pos) = self["PositionGauge"].get()
316                         
317                         if number == 4:
318                                 self.seek_target -= len / 10
319                         else:
320                                 self.seek_target += len / 10
321                         
322                         if self.seek_target > len * 9 / 10:
323                                 self.seek_target = len * 9 / 10
324                         
325                         if self.seek_target < 0:
326                                 self.seek_target = 0
327
328                 print "seek target is now", self.seek_target
329                 
330                 self.updateSeek()
331         
332         def updateSeek(self):
333                 if self.seek_target is None:
334                         self["PositionGauge"].seek_pointer = False
335                 else:
336                         self["PositionGauge"].seek_pointer = True
337                         self["PositionGauge"].seek_pointer_position = self.seek_target
338
339
340         def showMenu(self):
341                 menu = []
342                 if self.currList == "filelist":
343                         menu.append((_("switch to playlist"), "playlist"))
344                         if self.filelist.canDescent():
345                                 menu.append((_("add directory to playlist"), "copydir"))
346                         else:
347                                 menu.append((_("add file to playlist"), "copy"))
348                 else:
349                         menu.append((_("switch to filelist"), "filelist"))
350                         menu.append((_("delete"), "delete"))
351                         menu.append((_("clear playlist"), "clear"))
352                 menu.append((_("hide player"), "hide"));
353                 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
354
355         def menuCallback(self, choice):
356                 if choice is None:
357                         return
358
359                 if choice[1] == "copydir":
360                         self.copyDirectory(self.filelist.getSelection()[0])
361                 elif choice[1] == "copy":
362                         self.copyFile()
363                 elif choice[1] == "playlist":
364                         self.switchToPlayList()
365                 elif choice[1] == "filelist":
366                         self.switchToFileList()
367                 elif choice[1] == "delete":
368                         if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
369                                 self.stopEntry()
370                         self.deleteEntry()
371                 elif choice[1] == "clear":
372                         self.stopEntry()
373                         self.playlist.clear()
374                         self.switchToFileList()
375                 elif choice[1] == "hide":
376                         self.hide()
377
378         def copyDirectory(self, directory, recursive = True):
379                 print "copyDirectory", directory
380                 filelist = FileList(directory, useServiceRef = True, isTop = True)
381
382                 for x in filelist.getFileList():
383                         if x[0][1] == True: #isDir
384                                 if recursive:
385                                         self.copyDirectory(x[0][0])
386                         else:
387                                 self.playlist.addFile(x[0][0])
388                 self.playlist.updateList()
389
390         ADDPLAYLIST = 0
391         REPLACEPLAYLIST = 1
392
393         def copyFile(self):
394                 if self.filelist.getServiceRef().type == 4098: # playlist
395                         list = []
396                         list.append((_("Add files to playlist"), (self.ADDPLAYLIST, self.filelist.getServiceRef())))
397                         list.append((_("Replace current playlist"), (self.REPLACEPLAYLIST, self.filelist.getServiceRef())))
398                         self.session.openWithCallback(self.playlistCallback, ChoiceBox, title=_("You selected a playlist"), list = list)
399                 else:
400                         self.playlist.addFile(self.filelist.getServiceRef())
401                         self.playlist.updateList()
402                         if len(self.playlist) == 1:
403                                 self.changeEntry(0)
404
405         def addPlaylistParser(self, parser, extension):
406                 self.playlistparsers[extension] = parser
407
408         def playlistCallback(self, answer):
409                 if answer is not None:
410                         extension = answer[1][1].getPath()[answer[1][1].getPath().rfind('.') + 1:]
411                         print "extension:", extension
412                         if self.playlistparsers.has_key(extension):
413                                 playlist = self.playlistparsers[extension]()
414                                 if answer[1][0] == self.REPLACEPLAYLIST:
415                                         self.stopEntry()
416                                         self.playlist.clear()
417                                         self.switchToFileList()
418                                 if answer[1][0] == self.REPLACEPLAYLIST or answer[1][0] == self.ADDPLAYLIST:
419                                         list = playlist.open(answer[1][1].getPath())
420                                         for x in list:
421                                                 self.playlist.addFile(x.ref)
422
423
424         def nextEntry(self):
425                 next = self.playlist.getCurrentIndex() + 1
426                 if next < len(self.playlist):
427                         self.changeEntry(next)
428
429         def previousEntry(self):
430                 next = self.playlist.getCurrentIndex() - 1
431                 if next >= 0:
432                         self.changeEntry(next)
433
434         def deleteEntry(self):
435                 self.playlist.deleteFile(self.playlist.getSelectionIndex())
436                 self.playlist.updateList()
437                 if len(self.playlist) == 0:
438                         self.switchToFileList()
439
440         def changeEntry(self, index):
441                 self.playlist.setCurrentPlaying(index)
442                 self.playEntry()
443
444         def playServiceRefEntry(self, serviceref):
445                 serviceRefList = self.playlist.getServiceRefList()
446                 for count in range(len(serviceRefList)):
447                         if serviceRefList[count] == serviceref:
448                                 self.changeEntry(count)
449                                 break
450
451         def playEntry(self):
452                 if len(self.playlist.getServiceRefList()):
453                         currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
454                         if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
455                                 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
456                                 info = eServiceCenter.getInstance().info(currref)
457                                 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
458                                 self["title"].setText(description)
459                         self.unPauseService()
460
461         def updatedSeekState(self):
462                 if self.seekstate == self.SEEK_STATE_PAUSE:
463                         self.playlist.pauseFile()
464                 elif self.seekstate == self.SEEK_STATE_PLAY:
465                         self.playlist.playFile()
466                 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
467                                                                  self.SEEK_STATE_FF_4X,
468                                                                  self.SEEK_STATE_FF_8X,
469                                                                  self.SEEK_STATE_FF_32X,
470                                                                  self.SEEK_STATE_FF_64X,
471                                                                  self.SEEK_STATE_FF_128X):
472                         self.playlist.forwardFile()
473                 elif self.seekstate in ( self.SEEK_STATE_BACK_16X,
474                                                                  self.SEEK_STATE_BACK_32X,
475                                                                  self.SEEK_STATE_BACK_64X,
476                                                                  self.SEEK_STATE_BACK_128X,):
477                         self.playlist.rewindFile()
478
479         def pauseEntry(self):
480                 self.pauseService()
481
482         def stopEntry(self):
483                 self.playlist.stopFile()
484                 self.session.nav.playService(None)
485                 self.updateMusicInformation(clear=True)
486
487         def unPauseService(self):
488                 self.setSeekState(self.SEEK_STATE_PLAY)