/** * Copyright 2009 Marc Stogaitis and Mimi Sun * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gmote.server.media; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import org.blinkenlights.jid3.MP3File; import org.gmote.common.TcpConnection; import org.gmote.common.media.MediaMetaInfo; import org.gmote.common.packet.AbstractPacket; import org.gmote.common.packet.MediaInfoPacket; import org.gmote.common.packet.MediaInfoReqPacket; /** * Periodically polls information about the currently playing media and sends it * to the phone. This is needed for media players that do not allow us to * receive a callback when media changes (such as a playlist going to the next * song, or the user changing the song directly on the computer's user * interface. * * @author Marc Stogaitis */ public class MediaInfoUpdater { private static final Logger LOGGER = Logger.getLogger(MediaInfoUpdater.class.getName()); private static MediaInfoUpdater instance = null; private static final int MEDIA_INFO_UPDATE_DELAY = 5000; private byte[] lastGeneratedImageData = null; Timer pollingTimer = new Timer("MediaInfoTimer"); MediaPlayerInterface mediaPlayer; TcpConnection con = null; // Private constructor to prevent instantiation. private MediaInfoUpdater() { } public static MediaInfoUpdater instance() { if (instance == null) { instance = new MediaInfoUpdater(); } return instance; } public void setClientConnection(TcpConnection con) { this.con = con; changePollingState(); } /** * Sends information about the currently playing media to the client. * * @param mediaInfo */ public synchronized void sendMediaUpdate(MediaMetaInfo mediaInfo) { if (mediaInfo == null || con == null) { return; } else { try { LOGGER.info("Sending media info update"); con.sendPacket(new MediaInfoPacket(mediaInfo)); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); setClientConnection(null); } } } public synchronized void setPlayerToPoll(MediaPlayerInterface mediaPlayer) { this.mediaPlayer = mediaPlayer; changePollingState(); } private boolean shouldPoll() { return mediaPlayer != null; } private boolean clientIsConnected() { return con != null; } private void changePollingState() { if (!clientIsConnected()) { pollingTimer.cancel(); } else if (!shouldPoll()) { pollingTimer.cancel(); } else { pollingTimer.cancel(); pollingTimer = new Timer("MediaInfoTimer"); pollingTimer.schedule(new UpdateTask(), MEDIA_INFO_UPDATE_DELAY, MEDIA_INFO_UPDATE_DELAY); } } class UpdateTask extends TimerTask { @Override public synchronized void run() { sendMediaUpdate(mediaPlayer.getNewMediaInfo()); } } /** * Convenience function to generate information about media based only on a * file on the local disc. Most media players have access to more information * than this and will therefore implement their own algorithm. * @param forceImageUpdate */ public MediaMetaInfo generateMediaMetaInfo(String fileName, boolean forceImageUpdate) { MP3File mp3 = new MP3File(new File(fileName)); MediaMetaInfo fileInfo = PlayerUtil.getSongMetaInfo(mp3); byte[] imageData = PlayerUtil.extractEmbeddedImageData(mp3); if (imageData == null) { // In windows, a folder.jpg file often contains the album art imageData = PlayerUtil.extractImageFromFolder(fileName); } boolean imageIsSame = lastGeneratedImageData != null && imageData != null && Arrays.equals(lastGeneratedImageData, imageData); if (imageIsSame && !forceImageUpdate) { fileInfo.setImageSameAsPrevious(true); } else { lastGeneratedImageData = imageData; fileInfo.setImage(imageData); } return fileInfo; } /** * Returns a packet with the latest media info. */ public AbstractPacket handleMediaInfoReq(AbstractPacket packet) { MediaInfoReqPacket mediaInfoReq = (MediaInfoReqPacket) packet; if (new File(mediaInfoReq.getPathAndFileName()).exists()) { MediaMetaInfo fileInfo = generateMediaMetaInfo(mediaInfoReq.getPathAndFileName(), mediaInfoReq.isForceImageUpdate()); if (fileInfo.getTitle() != null || fileInfo.getArtist() != null || fileInfo.getImage() != null) { return new MediaInfoPacket(fileInfo); } } return null; } }