Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#223 Fix incrementPlayCount method to avoid counting no played media #416

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ public void stop(@DestinationVariable("playerId") Integer playerId, SimpMessageH
playQueueService.stop(player);
}

@MessageMapping("/endMedia")
public void endMedia(@DestinationVariable("playerId") Integer playerId, Integer mediaFileId, SimpMessageHeaderAccessor headers) throws Exception {
if (mediaFileId == null) {
return;
}
Player player = getPlayer(playerId, headers);
playQueueService.endMedia(player, mediaFileId);
}

@MessageMapping("/toggleStartStop")
public void toggleStartStop(@DestinationVariable("playerId") Integer playerId, SimpMessageHeaderAccessor headers) throws Exception {
Player player = getPlayer(playerId, headers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public ResponseEntity<Resource> handleSegmentRequest(Authentication auth,
BiConsumer<InputStream, TransferStatus> inputStreamInit = (i, s) -> {
LOG.info("{}: {} listening to {}", player.getIpAddress(), player.getUsername(), FileUtil.getShortPath(mediaFile.getRelativePath()));
if (segmentIndex == 0)
this.mediaFileService.incrementPlayCount(mediaFile);
this.mediaFileService.incrementPlayCount(player, mediaFile);
};

Resource resource = new MonitoredResource(new PathResource(segmentFile),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,17 @@ public ResponseEntity<Resource> handleRequest(Authentication authentication,

Consumer<MediaFile> fileStartListener = mediaFile -> {
LOG.info("{}: {} listening to {} in folder {}", player.getIpAddress(), player.getUsername(), FileUtil.getShortPath(mediaFile.getRelativePath()), mediaFile.getFolder().getId());
mediaFileService.incrementPlayCount(mediaFile);
scrobble(mediaFile, player, false);
status.setMediaFile(mediaFile);
statusService.addActiveLocalPlay(
new PlayStatus(status.getId(), mediaFile, player, status.getMillisSinceLastUpdate()));
};
Consumer<MediaFile> fileEndListener = mediaFile -> {
BiConsumer<Integer, MediaFile> fileEndListener = (readCount, mediaFile) -> {
if (readCount != null && readCount > 0 && player.getTechnology() != PlayerTechnology.WEB) {
// Increment play count if the file was actually played
// WEB player increments play count on the client side
mediaFileService.incrementPlayCount(player, mediaFile);
}
scrobble(mediaFile, player, true);
statusService.removeActiveLocalPlay(
new PlayStatus(status.getId(), mediaFile, player, status.getMillisSinceLastUpdate()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1442,7 +1442,7 @@ public void scrobble(HttpServletRequest request, HttpServletResponse response) t
Instant time = times.length == 0 ? null : Instant.ofEpochMilli(times[i]);

statusService.addRemotePlay(new PlayStatus(UUID.randomUUID(), file, player, time == null ? Instant.now() : time));
mediaFileService.incrementPlayCount(file);
mediaFileService.incrementPlayCount(player, file);
audioScrobblerService.register(file, player.getUsername(), submission, time);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpSer
// map.put("bitRates", BIT_RATES);
map.put("defaultBitRate", streamUrls.getLeft());
map.put("user", user);
map.put("playerId", playerId);

return new ModelAndView("videoPlayer", "model", map);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public class PlayQueueInputStream extends InputStream {
private final PlayQueue queue;
private final Consumer<MediaFile> fileStartListener;
private final Consumer<MediaFile> fileEndListener;
private final BiConsumer<Integer, MediaFile> fileEndListener;
private final Function<MediaFile, InputStream> streamGenerator;
private InputStream currentStream;
private MediaFile currentFile;
private Integer readCount = 0;

public PlayQueueInputStream(PlayQueue queue, Consumer<MediaFile> fileStartListener,
Consumer<MediaFile> fileEndListener, Function<MediaFile, InputStream> streamGenerator) {
BiConsumer<Integer, MediaFile> fileEndListener, Function<MediaFile, InputStream> streamGenerator) {
this.queue = queue;
this.fileStartListener = fileStartListener;
this.fileEndListener = fileEndListener;
Expand Down Expand Up @@ -71,6 +73,8 @@ private void prepare() throws IOException {
currentFile = file;
fileStartListener.accept(currentFile);
currentStream = streamGenerator.apply(currentFile);
} else {
readCount++;
}
}

Expand All @@ -80,9 +84,10 @@ public void closeStream() throws IOException {
currentStream = null;
}
if (currentFile != null) {
fileEndListener.accept(currentFile);
fileEndListener.accept(readCount, currentFile);
currentFile = null;
}
readCount = 0;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private void onSongStart(MediaFile file) {
status = statusService.createStreamStatus(player);
status.setMediaFile(file);
status.addBytesTransferred(file.getFileSize());
mediaFileService.incrementPlayCount(file);
mediaFileService.incrementPlayCount(status.getPlayer(), file);
playStatus = new PlayStatus(status.getId(), file, status.getPlayer(), status.getMillisSinceLastUpdate());
statusService.addActiveLocalPlay(playStatus);
scrobble(file, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
Expand Down Expand Up @@ -130,6 +131,8 @@ public class MediaFileService {

private final double DURATION_EPSILON = 1e-2;

private final Map<Integer, Pair<Integer, Instant>> lastPlayed = new ConcurrentHashMap<>();

public MediaFile getMediaFile(String pathName) {
return getMediaFile(Paths.get(pathName));
}
Expand Down Expand Up @@ -1482,8 +1485,16 @@ public void updateMediaFile(@Nonnull MediaFile mediaFile) {
* directory and album.
*/
@Transactional
public void incrementPlayCount(MediaFile file) {
public void incrementPlayCount(Player player, MediaFile file) {
Instant now = Instant.now();

Pair<Integer, Instant> lastPlayedInfo = lastPlayed.computeIfAbsent(player.getId(), k -> Pair.of(file.getId(), now));
if (lastPlayedInfo.getLeft() == file.getId()) {
Double threshold = Math.max(1.0, file.getDuration() / 2);
if (Duration.between(lastPlayedInfo.getRight(), now).getSeconds() < threshold) {
return;
}
}
file.setLastPlayed(now);
file.setPlayCount(file.getPlayCount() + 1);
updateMediaFile(file);
Expand All @@ -1501,6 +1512,8 @@ public void incrementPlayCount(MediaFile file) {
albumRepository.save(album);
}
);

lastPlayed.put(player.getId(), Pair.of(file.getId(), now));
}

public List<MediaFileEntry> toMediaFileEntryList(List<MediaFile> files, String username, boolean calculateStarred, boolean calculateFolderAccess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Nonnull;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -86,6 +88,14 @@ public void stop(Player player) {
PlayQueue.Status.STOPPED);
}

public void endMedia(Player player, @Nonnull Integer mediaFileId) {
MediaFile file = mediaFileService.getMediaFile(mediaFileId);
if (file == null) {
return;
}
mediaFileService.incrementPlayCount(player, file);
}

public void toggleStartStop(Player player) {
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
start(player);
Expand Down
4 changes: 3 additions & 1 deletion airsonic-main/src/main/resources/templates/playQueue.html
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@

onEnded() {
this.setBookmark();
var song = this.songs[this.currentSongIndex];
top.StompClient.send("/app/playqueues/" + this.player.id + "/endMedia", song.id);
this.onNext(this.repeatStatus);
},

Expand Down Expand Up @@ -711,7 +713,7 @@
castAppID: "4FBFE470",
features: ["speed", "chromecast"],
defaultSpeed: "1.00",
speeds: ["8.00", "2.00", "1.50", "1.25", "1.00", "0.75", "0.5"],
speeds: ["2.00", "1.50", "1.25", "1.00", "0.75", "0.5"],
success(mediaElement, originalNode, instance) {
// "hack" html5 renderer and reinitialize speed
instance.media.rendererName = "html5";
Expand Down
12 changes: 9 additions & 3 deletions airsonic-main/src/main/resources/templates/videoPlayer.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
videoBookmarkFrequency: /*[(${model.videoBookmarkFrequency})]*/ 30,
contentType: "[(${model.contentType})]",
hideShare: /*[(${model.user.shareRole ? 'true': 'false'})]*/ false,
hideDownload: /*[(${model.user.downloadRole ? 'true': 'false'})]*/ false
hideDownload: /*[(${model.user.downloadRole ? 'true': 'false'})]*/ false,
playerId: /*[(${model.playerId})]*/ 0
}

function setBookmark() {
Expand All @@ -74,6 +75,11 @@
}
}
}
function onEnded() {
this.setBookmark();
top.StompClient.send("/app/playqueues/" + videoModel.playerId + "/endMedia", videoModel.videoId);
}


function init() {
$.get(videoModel.remoteCaptionsListUrl, data => {
Expand All @@ -95,7 +101,7 @@
path: "[(@{/script/mediaelement/renderers/dash.all-4.6.0.min.js})]"
},
defaultSpeed: "1.00",
speeds: ["8.00", "2.00", "1.50", "1.25", "1.00", "0.75", "0.5"],
speeds: ["2.00", "1.50", "1.25", "1.00", "0.75", "0.5"],
defaultQuality: "[(${model.defaultBitRate})]",
videoWidth: "100%",
videoHeight: "100%",
Expand Down Expand Up @@ -123,7 +129,7 @@
instance.setCurrentTime(videoModel.position/1000);

// Once playback reaches the end, go to the next song, if any.
$(mediaElement).on("ended", () => vpr.setBookmark());
$(mediaElement).on("ended", () => vpr.onEnded());
$(mediaElement).on("timeupdate", () => vpr.setBookmark());
$(mediaElement).on("seeked", () => vpr.setBookmark());
$(mediaElement).on("paused", () => vpr.setBookmark());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public void testUpdateJukeBoxWithPlayingPlayerShouldPlay() throws Exception {

// onSongStart
verify(statusService).createStreamStatus(mockedPlayer);
verify(mediaFileService).incrementPlayCount(mockedMediaFile);
verify(mediaFileService).incrementPlayCount(mockedPlayer, mockedMediaFile);
verify(statusService).addActiveLocalPlay(any());

// scrobble
Expand Down Expand Up @@ -254,7 +254,7 @@ public void testUpdateJukeBoxWithPlayingPlayerShouldUpdatePlayer() throws Except
verify(secondAudioPlayer).setGain(eq(0.75f)); // default gain
verify(secondAudioPlayer).play();
verify(statusService).createStreamStatus(secondPlayer);
verify(mediaFileService).incrementPlayCount(secondMediaFile);
verify(mediaFileService).incrementPlayCount(secondPlayer, secondMediaFile);
verify(audioScrobblerService).register(eq(secondMediaFile), eq("test"), eq(false), isNull());
}

Expand Down Expand Up @@ -292,7 +292,7 @@ public void testStateChangedShouldPlayNext() throws Exception {
verify(mockedAudioPlayer, times(2)).setGain(eq(0.75f)); // default gain
verify(mockedAudioPlayer, times(2)).play();
verify(statusService, times(2)).createStreamStatus(mockedPlayer);
verify(mediaFileService).incrementPlayCount(secondMediaFile);
verify(mediaFileService).incrementPlayCount(mockedPlayer, secondMediaFile);
verify(audioScrobblerService).register(eq(secondMediaFile), eq("test"), eq(false), isNull());

}
Expand Down