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