package me.moodcat.backend.rooms; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import me.moodcat.backend.UnitOfWorkSchedulingService; import me.moodcat.database.controllers.SongDAO; import me.moodcat.database.entities.Song; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; /** * A song that is currently playing in a room. */ public class SongInstance { /** * SongDAO provider for the context. */ private final Provider<SongDAO> songDAOProvider; /** * The current time of the room's song. */ private final AtomicLong currentTime; /** * The duration of the song. */ private final int duration; /** * Last update. */ private final AtomicLong lastUpdate; /** * Stopped. */ private final AtomicBoolean stopped; /** * {@link me.moodcat.backend.UnitOfWorkSchedulingServiceImpl} to schedule tasks in a unit of work. */ private final UnitOfWorkSchedulingService unitOfWorkSchedulingService; /** * Song id for the song. */ private final int songId; /** * Stop observers. */ private final List<StopObserver> observers; /** * Create a new song instance. * * @param songDAOProvider * The SongDAO provider. * @param song * The song this instance presents. */ @AssistedInject public SongInstance(final Provider<SongDAO> songDAOProvider, final UnitOfWorkSchedulingService unitOfWorkSchedulingService, @Assisted final Song song) { assert songDAOProvider != null; Preconditions.checkNotNull(song); this.songDAOProvider = songDAOProvider; this.unitOfWorkSchedulingService = unitOfWorkSchedulingService; this.currentTime = new AtomicLong(0L); this.stopped = new AtomicBoolean(false); this.observers = Lists.newLinkedList(); this.songId = song.getId(); this.duration = song.getDuration(); this.lastUpdate = new AtomicLong(System.currentTimeMillis()); final ScheduledFuture<?> future = this.unitOfWorkSchedulingService .scheduleAtFixedRate(this::incrementTime, 1L, 1L, TimeUnit.SECONDS); // Observer: Stop the increment time task when the song is finished this.addObserver(() -> future.cancel(false)); } /** * Add stoped observer. * * @param stopObserver * Observer to be called when this song stops. */ public void addObserver(final StopObserver stopObserver) { this.observers.add(stopObserver); } /** * Get the song for this song instance. * * @return * The song that is playing */ public Song getSong() { return this.songDAOProvider.get().findById(songId); } /** * Stop this song instance. */ public void stop() { if (!this.stopped.getAndSet(true)) { this.observers.forEach(StopObserver::stopped); } } /** * Method used to increment the time of the current song by one second. */ protected void incrementTime() { if (!this.stopped.get()) { final long now = System.currentTimeMillis(); final long then = lastUpdate.getAndSet(now); final long cur = currentTime.addAndGet(now - then); if (cur > duration) { this.stop(); } } } /** * Check if the song has completed. * * @return true if the song is finished */ public boolean isStopped() { return this.stopped.get(); } /** * Get the time. * * @return the time */ public long getTime() { return currentTime.get(); } /** * Observer to notify the song has stopped. */ @FunctionalInterface public interface StopObserver { /** * Called when this song has stopped. */ void stopped(); } }