Skip to content

Commit

Permalink
Merge pull request streamaserver#1062 from streamaserver/dev/AL/add-l…
Browse files Browse the repository at this point in the history
…oadmore-continue-watching-feature

Dev/al/add loadmore continue watching feature
  • Loading branch information
dularion authored Apr 8, 2021
2 parents d91e986 + fe3f07a commit d1ec14d
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 114 deletions.
83 changes: 24 additions & 59 deletions grails-app/assets/javascripts/streama/controllers/dash-ctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ angular.module('streama').controller('dashCtrl',
function ($scope, apiService, $state, $rootScope, localStorageService, modalService, $stateParams, mediaListService, currentUser ) {
var vm = this;

var LIST_MAX = 30;
vm.showDetails = showDetails;
vm.handleWatchlistUpdate = handleWatchlistUpdate;
vm.loadingRecommendations = true;
Expand All @@ -30,8 +29,8 @@ angular.module('streama').controller('dashCtrl',
var data = response.data;
localStorageService.set('currentProfile', data[0]);
initMedia();
}
, function (data) {
},
function (data) {
alertify.error(data.message);
});
} else {
Expand All @@ -40,19 +39,21 @@ angular.module('streama').controller('dashCtrl',
}

function initMedia() {
if(isDashType("home") || isDashType("discover-movies")){
if(isDashType('home') || isDashType('discover-movies')){
vm.movie = mediaListService.init(apiService.dash.listMovies, {sort: 'title', order: 'ASC'}, currentUser);
}
if(isDashType("home") || isDashType("discover-shows")){
if(isDashType('home') || isDashType('discover-shows')){
vm.tvShow = mediaListService.init(apiService.dash.listShows, {sort: 'name', order: 'ASC'}, currentUser);
}
if(isDashType("home") || isDashType("watchlist")){
if(isDashType('home') || isDashType('continue-watching')){
vm.continueWatching = mediaListService.init(apiService.dash.listContinueWatching, {sort: 'lastUpdated', order: 'DESC'}, currentUser);
}
if(isDashType('home') || isDashType('watchlist')){
vm.watchlistEntry = mediaListService.init(apiService.watchlistEntry.list, {sort: 'id', order: 'DESC'}, currentUser);
}
if(isDashType("home")){
if(isDashType('home')){
vm.genericVideo = mediaListService.init(apiService.dash.listGenericVideos, {sort: 'title', order: 'ASC'}, currentUser);
apiService.dash.listNewReleases().then(onNewReleasesLoaded);
apiService.dash.listContinueWatching().then(onContinueWatchingLoaded);
apiService.dash.listRecommendations().then(onRecommendedLoaded);
}

Expand All @@ -72,24 +73,6 @@ angular.module('streama').controller('dashCtrl',
vm.loadingRecommendations = false;
}

function fetchData(mediaConfig) {
mediaConfig.fetch({max: LIST_MAX, offset: mediaConfig.currentOffset, sort: mediaConfig.currentSort.sort, order: mediaConfig.currentSort.order}).then(function (response) {
var data = response.data;
mediaConfig.total = data.total;
if(mediaConfig.currentOffset > 0){
mediaConfig.list = _.unionBy(mediaConfig.list, data.list, 'id');
}else{
mediaConfig.list = data.list;
}
mediaConfig.isLoading = false;
});
}

function onContinueWatchingLoaded(response) {
var data = response.data;
vm.continueWatching = data;
}

function onNewReleasesLoaded(response) {
var data = response.data;
vm.newReleases = data;
Expand Down Expand Up @@ -153,10 +136,10 @@ angular.module('streama').controller('dashCtrl',

function handleWatchlistUpdate(action, item){
switch (action) {
case "added":
case 'added':
addToWatchlist(item);
break;
case "removed":
case 'removed':
removeFromWatchlist(item);
break;
}
Expand All @@ -165,17 +148,17 @@ angular.module('streama').controller('dashCtrl',
function addToWatchlist(item) {
apiService.watchlistEntry.create(item).then(function (response) {
vm.watchlistEntry.list = vm.watchlistEntry.list ? vm.watchlistEntry.list : [];
updateWatchlist("added", _.get(vm.watchlistEntry, 'list'), item, response.data);
updateWatchlist('added', _.get(vm.watchlistEntry, 'list'), item, response.data);
});
}

function removeFromWatchlist(item) {
vm.watchlistEntry.list = vm.watchlistEntry.list ? vm.watchlistEntry.list : [];
alertify.set({buttonReverse: true, labels: {ok: "Yes", cancel: "Cancel"}});
alertify.confirm("Are you sure you want to remove this video from your watchlist?", function (confirmed) {
alertify.set({buttonReverse: true, labels: {ok: 'Yes', cancel: 'Cancel'}});
alertify.confirm('Are you sure you want to remove this video from your watchlist?', function (confirmed) {
if (confirmed) {
apiService.watchlistEntry.delete(item).then(function (response) {
updateWatchlist("removed", _.get(vm.watchlistEntry, 'list'), item);
updateWatchlist('removed', _.get(vm.watchlistEntry, 'list'), item);
});
}
});
Expand All @@ -190,27 +173,27 @@ angular.module('streama').controller('dashCtrl',
removeMediaFromList(list, media);
alertify.success('The '+type+' was removed from your watchlist.');
}
list.sort(function(a,b) { return (a.id < b.id) ? 1 : ((a.id > b.id) ? -1 : 0)});
list.sort(function(a,b) { return (a.id < b.id) ? 1 : ((a.id > b.id) ? -1 : 0);});
}

function removeMediaFromList(list, media){
_.remove(list, function (watchlistEntry) {
return (watchlistEntry.video ? watchlistEntry.video.id : watchlistEntry.tvShow.id) === media.id
return (watchlistEntry.video ? watchlistEntry.video.id : watchlistEntry.tvShow.id) === media.id;
});
}

function handleVideoListsUpdate(media){
var type = media.mediaType;
switch (type) {
case "tvShow":
case 'tvShow':
watchlistStatusHandler(vm.tvShow.list, media);
type = "show";
type = 'show';
break;
case "movie":
case 'movie':
watchlistStatusHandler(vm.movie.list, media);
type = 'movie';
break;
case "genericVideo":
case 'genericVideo':
watchlistStatusHandler(vm.genericVideo.list, media);
type = 'video';
break;
Expand All @@ -219,34 +202,16 @@ angular.module('streama').controller('dashCtrl',
}
watchlistStatusHandler(vm.newReleases, media);
watchlistStatusHandler(vm.continueWatching, media);
return type
return type;
}

function watchlistStatusHandler(mediaList, item){
var index = _.findIndex(mediaList, function(element) { return item.id === element.id});
var index = _.findIndex(mediaList, function(element) { return item.id === element.id;});
if(index > 0){
mediaList[index].inWatchlist = !mediaList[index].inWatchlist
mediaList[index].inWatchlist = !mediaList[index].inWatchlist;
}
}

function applyFilter(item, filterObj) {
var showItemArray = [];

_.forEach(filterObj, function (filterVal, key) {
if (_.isArray(filterVal) && filterVal.length) {
var intersection = _.intersectionBy(item[key], filterVal, 'id');
var isVisible = (intersection.length ? true : false);
showItemArray.push(isVisible);
}
if (_.isString(filterVal) && filterVal.length >= 1) {
var isVisible = (_.includes(item[key].toLowerCase(), filterVal.toLowerCase()) ? true : false);
showItemArray.push(isVisible);
}
});

return (showItemArray.indexOf(false) < 0);
}

function isDashSectionHidden(sectionName) {
var hiddenDashSectionSetting = _.find($scope.settings, {name: 'hidden_dash_sections'});
if(_.get(hiddenDashSectionSetting, 'parsedValue')){
Expand Down
14 changes: 7 additions & 7 deletions grails-app/assets/javascripts/streama/services/api-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ angular.module('streama').factory('apiService', function ($http, $rootScope, con
return $http.post('watchlistEntry/create.json', {params: {id: item.id, mediaType: item.mediaType}});
},
delete: function (item) {
return $http.delete('watchlistEntry/delete.json', {params: {id: item.id, mediaType: item.mediaType}})
return $http.delete('watchlistEntry/delete.json', {params: {id: item.id, mediaType: item.mediaType}});
},
list: function (params) {
return $http.get('watchlistEntry/list.json', {params: params});
Expand Down Expand Up @@ -341,8 +341,8 @@ angular.module('streama').factory('apiService', function ($http, $rootScope, con
return $http.get('dash/searchMedia.json', {params: {query: query}});
},

listContinueWatching: function () {
return $http.get('dash/listContinueWatching.json');
listContinueWatching: function (params) {
return $http.get('dash/listContinueWatching.json', {params: params});
},

listMovies: function (params) {
Expand Down Expand Up @@ -386,16 +386,16 @@ angular.module('streama').factory('apiService', function ($http, $rootScope, con

profile: {
save: function (params) {
return $http.post('profile/save', params)
return $http.post('profile/save', params);
},
update: function (params) {
return $http.put('profile/update.json', params)
return $http.put('profile/update.json', params);
},
delete: function (id) {
return $http.delete('profile/delete.json', {params: {id: id}})
return $http.delete('profile/delete.json', {params: {id: id}});
},
getUserProfiles: function () {
return $http.get('profile/getUserProfiles.json')
return $http.get('profile/getUserProfiles.json');
}
}

Expand Down
11 changes: 8 additions & 3 deletions grails-app/assets/javascripts/streama/templates/dash.tpl.htm
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ <h2 class="genre-display" ng-if="$root.selectedGenre">{{'DASHBOARD.LOOKING_AT_GE

<!----------------------------------------------continue watching-------------------------------------------->

<div ng-if="vm.continueWatching.length && !$root.selectedGenre && vm.isDashType('home') && !vm.isDashSectionHidden('continue-watching')">
<div ng-if="vm.continueWatching.list.length && !$root.selectedGenre && vm.isDashType('home') && !vm.isDashSectionHidden('continue-watching')">
<hr/>

<h3>{{'DASHBOARD.CONTINUE_WATCHING' | translate}}</h3>
<h3>{{'DASHBOARD.CONTINUE_WATCHING' | translate}}
<span class="counter">({{vm.continueWatching.list.length}} {{'DASHBOARD.TITLE_COUNTER_OF' | translate}} {{vm.continueWatching.total}})</span>
</h3>

<div class="media-list media-list-continue-watching">
<div class="media-list-item media-poster-item" ng-repeat="viewingStatus in vm.continueWatching" ng-if="!viewingStatus.video.deleted">
<div class="media-list-item media-poster-item" ng-repeat="viewingStatus in vm.continueWatching.list" ng-if="!viewingStatus.video.deleted">

<streama-dash-media-item entity="viewingStatus.video"></streama-dash-media-item>

Expand All @@ -65,6 +67,9 @@ <h3>{{'DASHBOARD.CONTINUE_WATCHING' | translate}}</h3>
</div>
</div>
</div>
<div class="text-center" ng-if="vm.continueWatching.total > vm.continueWatching.list.length">
<button class="btn btn-primary btn-outline" ng-click="vm.continueWatching.loadMore()">Load more ...</button>
</div>
</div>

<!----------------------------------------------recommendations-------------------------------------------->
Expand Down
5 changes: 1 addition & 4 deletions grails-app/controllers/streama/DashController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ class DashController {
User currentUser = springSecurityService.currentUser
Long profileId = request.getHeader('profileId')?.toLong()
Profile profile = Profile.findById(profileId)

List<ViewingStatus> viewingStatusList = videoService.listContinueWatching(currentUser, profile)

return [viewingStatusList: viewingStatusList]
respond videoService.listContinueWatching(currentUser, profile, params)
}

def listShows(){
Expand Down
2 changes: 2 additions & 0 deletions grails-app/controllers/streama/TheMovieDbController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class TheMovieDbController {
def results = json?.results

results.each{ hit ->
hit.backdrop_path = "https://image.tmdb.org/t/p/w300" + hit.backdrop_path
hit.poster_path = "https://image.tmdb.org/t/p/w300" + hit.poster_path
hit.genre = theMovieDbService.parseGenres(hit.genre_ids)
}

Expand Down
1 change: 1 addition & 0 deletions grails-app/domain/streama/ViewingStatus.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ViewingStatus {
Integer currentPlayTime
Integer runtime
Boolean completed = false
Boolean isActive = true
Profile profile

static Integer COMPLETED_PERCENTAGE_THRESHOLD = 95
Expand Down
1 change: 1 addition & 0 deletions grails-app/init/streama/BootStrap.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class BootStrap {
migrationService.migrateMergedSeasonEpisode()
migrationService.setupBasicSubProfiles()
migrationService.addProfilesToViewingStatusRecords()
migrationService.migrateContinueWatchingActiveFlag()

migrationService.dbMigrations()
}
Expand Down
37 changes: 37 additions & 0 deletions grails-app/services/streama/MigrationService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,43 @@ class MigrationService {
}
}

def migrateContinueWatchingActiveFlag(){
def continueWatchingQuery = ViewingStatus.where {
eq("completed", false)
isNull("isActive")
}.list(sort: 'lastUpdated', order: 'desc')

def groupedContinueWatchings = continueWatchingQuery.groupBy {it.profileId}

groupedContinueWatchings.each { profileId, continueWatchingList ->
def episodeResults = []
continueWatchingList.each{ viewingStatus ->
if(viewingStatus.video instanceof Movie || viewingStatus.video instanceof GenericVideo){
viewingStatus.isActive = true
viewingStatus.save()
return
}

if(viewingStatus.video instanceof Episode){
def previousShowEntry = episodeResults.find { it.video instanceof Episode && it.video.show?.id == viewingStatus.video.show?.id }
if(!previousShowEntry){
episodeResults.add(viewingStatus)
viewingStatus.isActive = true
viewingStatus.save()
}else{
viewingStatus.isActive = false
viewingStatus.save()
}
}

log.error("viewingStatus.video is neither Episode, Movie or GenericVideo - whats going on? ID: ${viewingStatus.id}")
}

}


}

def dbMigrations(){
def sql = new Sql(dataSource)
sql.execute('alter table genre modify api_id int null;')
Expand Down
47 changes: 10 additions & 37 deletions grails-app/services/streama/VideoService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -24,55 +24,28 @@ class VideoService {
}


public static List<ViewingStatus> listContinueWatching(User currentUser, Profile profile) {
List<ViewingStatus> continueWatching = ViewingStatus.withCriteria {
static Map listContinueWatching(User currentUser, Profile profile, GrailsParameterMap params) {
def max = params.int('max', 50)
def offset = params.int('offset', 0)
String sort = params.sort
String order = params.order
def continueWatchingQuery = ViewingStatus.where {
eq("user", currentUser)
eq("profile", profile)
eq("isActive", true)
video {
isNotEmpty("files")
ne("deleted", true)
}
// eq("completed", false)
order("lastUpdated", "desc")
}

return reduceContinueWatchingEps(continueWatching)
}

private static List<ViewingStatus> reduceContinueWatchingEps(List<ViewingStatus> continueWatching) {
def result = []
continueWatching.each { continueWatchingItem ->
if (continueWatchingItem.video instanceof Episode) {
def previousShowEntry = result.find { it.video instanceof Episode && it.video.show?.id == continueWatchingItem.video.show?.id }

if (!previousShowEntry) {
if(!continueWatchingItem.hasVideoEnded()){
result.add(continueWatchingItem)
}else{
continueWatchingItem.completed = true
continueWatchingItem.save()
ViewingStatus newViewingStatus = ViewingStatusService.createNewForNextEpisode(continueWatchingItem)
if(newViewingStatus){
result.add(newViewingStatus)
}
}
}
} else{
if(!continueWatchingItem.hasVideoEnded()){
result.add(continueWatchingItem)
}else{
continueWatchingItem.completed = true
continueWatchingItem.save()
}
}
}

return result
def viewingStatusList = continueWatchingQuery.list(max : max, offset: offset, sort: sort, order: order)
def totalCount = continueWatchingQuery.count()
return [total: totalCount, list: viewingStatusList]
}




@Transactional
def addLocalFile(Video videoInstance, params){
def result = [:]
Expand Down
Loading

0 comments on commit d1ec14d

Please sign in to comment.